風語·深蓝

Agile Methodology, HeadStorm And MindMap, they will change me.

导航

.NET Mocking Framework对比

Posted on 2010-12-08 00:16  風語者·疾風  阅读(1584)  评论(0编辑  收藏  举报

  单元测试中,为了让单元测试程序完全脱离外部依赖,需要使用到Mock对象和Stub对象。虽然可以手工编写Mock对象和Stub对象,但通常我们都使用Mocking Framework来帮助我们简单快速的构建需要的Mock对象以及Stub对象。

 

一、概述

  常见的Mocking Framework有如下几种:  

  1、Rhino Mocks V3.6(2009-9-1)

  Rhino Mocks是由Ayende Rahien 开发的一个开源项目,目前版本支持.NET 3.5 & 4.0以及Silverlight的CLR, 采用Castle DynamicProxy方式实现Mock对象的构建。

  采用Lambda表达式和泛型以及扩展方法实现调用API的强类型化。

  因为实现方式的限制,只能对Interface以及可继承的类进行Mock处理,对于sealed类、非virtual,非抽象以及静态方法则无能为力。

  因为考虑与旧版本的兼容性的问题, API较为繁琐,冗余方法较多,学习使用时容易混淆。

  被Mvccontrib项目所支持,可以被该项目的TestHelper调用,并用于ASP.NET中Stub对象的模拟。

  轻量级应用,不需要被安装,只需要在测试项目中进行引用即可实现对其的调用。

代码
    [TestFixture]
    
public class BrainTests
    {
        
/// <summary>
        
/// Verify that if hand throws an exception having touched a hot iron,  <see cref="IMouth.Yell"/> gets called.
        
/// </summary>
        
/// <remarks>
        
/// Rhino Mocks can mock both interfaces and classes - however, only virtual methods 
        
/// of a class can be mocked (try changing IHand/IMouth to Hand/Mouth).
        
/// </remarks>
        [Test]
        
public void TouchHotIron_Yell()
        {
            var hand 
= MockRepository.GenerateStub<IHand>();
            var mouth 
= MockRepository.GenerateMock<IMouth>();
            hand.Stub(h 
=> h.TouchIron(null)).Constraints(Is.Matching<Iron>(i => i.IsHot)).Throw(new BurnException());
            mouth.Expect(m 
=> m.Yell());

            var brain 
= new Brain(hand, mouth);
            brain.TouchIron(
new Iron { IsHot = true });

            mouth.VerifyAllExpectations();
        }
    }

 

2、Moq  V4.0.10827(2010-8-27)
  Moq是一个比较新的开源项目,最早提出了用Lambda表达式和泛型以及扩展方法实现调用API的强类型化,不用通过字符串方式描述Mock对象的方法以及属性,这个特点已经被其他Mocking Framework迅速吸收;

  Moq的语法及其简单,学习成本较低,也是最早提出隐性Record/Reply模式的Mocking Framework.

  Moq跟Rhino Mocks一样是采用Castle DynamicProxy方式实现Mock对象的构建,因此也无法对sealed类、非Virtual, 非抽象以及静态方法进行mock操作。

  被Mvccontrib项目所支持,可以被该项目的TestHelper调用,并用于ASP.NET中Stub对象的模拟。  

  轻量级应用,不需要被安装,只需要在测试项目中进行引用即可实现对其的调用。
代码
    [TestFixture]
    
public class BrainTests
    {
        
/// <summary>
        
/// Verify that if hand throws an exception having touched a hot iron,  <see cref="IMouth.Yell"/> gets called.
        
/// </summary>
        
/// <remarks>
        
/// Moq can mock both interfaces and classes - however, only virtual methods 
        
/// of a class can be mocked (try changing IHand/IMouth to Hand/Mouth).
        
/// </remarks>
        [Test]
        
public void TouchHotIron_Yell()
        {
            var hand 
= new Mock<IHand>();
            var mouth 
= new Mock<IMouth>();
            hand.Setup(x 
=> x.TouchIron(HotIron)).Throws(new BurnException());

            var brain 
= new Brain(hand.Object, mouth.Object);
            brain.TouchIron(
new Iron { IsHot = true });

            mouth.Verify(x 
=> x.Yell());    
        }

        
/// <summary>
        
/// Parameter expectations tend to be quite verbose in Moq, so we provide a custom matcher.
        
/// This needs a matcher method and a bool sibling method for evaluating the expectations.
        
/// Calling this matcher is technically equivalent to <code>It.Is{Iron}(i => i.IsHot)</code>.
        
/// </summary>
        public Iron HotIron
        {
            
get { return Match<Iron>.Create(x => x.IsHot); }
        }
    }

 

  3、Typemock 2010 商业软件,有试用版

  Typemock的实现方式比较特殊,它通过调用.NET Profiler接口,在代码运行时进行拦截,注入重定向代码,从而实现Mock处理。

  因为实现方式上的截然不同,也给予了Typemock更为强大的能力:可以Mock私有类,Sealed类以及非Virtual、非抽象甚至静态方法。

  也可以对mscorlib里定义的基本类型进行Mock,例如DateTime.Now。

  在使用API接口上,Typemock基本与Moq很类似,也是非常简单易用。

  使用Ivonna实现对ASP.NET环境下Stub对象的模拟。

  必须安装后集成到Visual Studio开发环境中才能使用,并且因为实现机制的原因,执行速度较慢。

代码
    [TestFixture]
    
public class BrainTests
    {
        
/// <summary>
        
/// Can mock both classes and interfaces, can mock private/static classes etc.
        
/// </summary>
        [Test, Isolated]
        
public void TouchHotIron_Yell()
        {
            var hand 
= Isolate.Fake.Instance<Hand>();
            var mouth 
= Isolate.Fake.Instance<Mouth>();
            var iron 
= new Iron { IsHot = true };
            Isolate.WhenCalled(() 
=> hand.TouchIron(iron)).WillThrow(new BurnException());

            var brain 
= new Brain(hand, mouth);
            brain.TouchIron(iron);

            Isolate.Verify.WasCalledWithAnyArguments(() 
=> mouth.Yell());
        }

        
/// <summary>
        
/// Can mock objects WITHOUT DEPENDENCY INJECTION.
        
/// </summary>
        [Test, Isolated]
        
public void TouchHotIron_Yell_NoDependencyInjection()
        {
            var hand 
= Isolate.Fake.Instance<Hand>();
            var mouth 
= Isolate.Fake.Instance<Mouth>();
            Isolate.Swap.NextInstance
<Hand>().With(hand);
            Isolate.Swap.NextInstance
<Mouth>().With(mouth);
            var iron 
= new Iron { IsHot = true };
            Isolate.WhenCalled(() 
=> hand.TouchIron(iron)).WillThrow(new BurnException());

            
//notice we're not passing the mocked objects in.
            var brain = new Brain();
            brain.TouchIron(iron);

            Isolate.Verify.WasCalledWithAnyArguments(() 
=> mouth.Yell());
        }
    }


  4、Moles V0.94.51006.1(2010-10-12)

   微软Research项目中的一员,非开源但免费使用,目前还未能发布正式版,在稳定性上稍微欠缺。
实现方式也是非常特殊:通过MSBuild的API,在编码编译时甚至编译后对Assembly进行重写,从而在已有的IL代码中插入重定向代码,从而实现Mock处理。

   Moles和Typemock一样,也是基本可以Mock所有的类和方法,甚至私有类,私有方法,也可以对mscorlib里定义的基本类型进行Mock。

   被处理过后的Assembly中直接生成了一些相应的Mock对象,因此Moles的API完全和其他Mocking Framework截然不同,而是采用约定的命名空间和首字母方式进行调用,给人感觉API比较怪异。

   Moles需要安装并集成到Visual Studio中,并且在安装过程中对.NET Framework中很多类库也进行了特殊处理,进而也可以引用相应的类库和方法实现对ASP.NET环境的支持。

 

二、横向比较

 


Rhino Mocks 3.6

Moq 4.0.10827

Typemock 2010

Moles 0.94.51006.1


实现方式

Castle Dynamic Proxy

Castle Dynamic Proxy

.NET Profiler

MS Build


授权情况

开源/免费

开源/免费

不开源/不免费

不开源/免费


强类型支持


递归Mocks对象

支持

支持

支持

支持


Partial Mocks

支持

支持

支持

支持


Virtual Method

支持

支持

支持

支持


Abstract Method

支持

支持

支持

支持


Public Class

支持

支持

支持

支持


Interface

支持

支持

支持

支持


Sealed Class 

不支持

不支持

支持

支持


Non-Abstract Method

不支持

不支持

支持

支持


Non-Virtual Method

不支持

不支持

支持

支持


Static Method

不支持

不支持

支持

支持


语法结构

一般

简炼

简炼

较差


对依赖注入框架的依赖性

依赖

依赖

不依赖

不依赖


对ASP.NET支持

MVC Contrib

MVC Contrib

Ivonna

自带


 

三、总结

 

  根据上面的概述和列表比较可以比较明显的看出:

      作为商业软件的Typemock确实除了在执行效率和安装使用方面存在一定的缺点外,基本是最为强大的Mocking Framework;

      而再看开源的Rhino Mocks和Moq基本非常接近,Moq在语法上更简炼易用一些(PS, 据说RM 4.0会大幅改进API),一般情况下这两个开源的Mocking Framework都应该可以满足需求,但他们都高度依赖于依赖注入框架,例如Unity, Autofac等等。不过关于这点,是优势还是劣势社区里也众说纷纭。不少支持者认为,正因为这样,强迫项目必须实现依赖倒置,从而降低了项目的耦合性,以达到较高的可测试性以及可维护性。而Typemock反而因为功能太过强大,无需依靠IOC容器反而受到开源社区的一些批评。

      最后再来看新秀Moles,这个东西从语法以及设计上都算是个另类,基本和其他框架没有相同之处,但功能上却也非常强大。不过从接口设计方面看,确实比较糟糕,不够优雅。另外,Moles有一个比较致命的弱点:因为Moles基于对程序集的编译后静态织入,因此,若被单元测试的程序集发生改变之后,必须整个重新运行编译再运行单元测试,这个在实际使用中是一个非常麻烦的事情。

 

      因此,在我看来,在项目实践中,若采用Typemock这个商业框架,则依然可以强制要求项目依赖于Ioc容器进行开发,保证不滥用Typemock的强大功能,只在不得不使用Typemock对mscorlib以及静态,私有等特殊方法进行mock时才使用。

      若采用免费方案,也许应该采用Moq为主,Moles为辅的方式进行:即使用Moq进行大部分的Mock操作,只有遇到当Moq无法解决的场景再使用Moles进行处理。

 

 注:文章中示例代码详见:mocking-frameworks-compare,该项目有详细的代码对比