目前针对测试驱动开发技术的研究在国内看来还少得可怜,估计这主要是国内的软件开发实际所致(急功近利型颇多)。所以,针对目前比较优秀的测试框架以及模拟框架的介绍也不多见。鉴于此,我想借这篇短文,对目前.NET平台上最新出炉的模拟对象框架—Moq作一简介。Moq的发明者声称,较之于其他的模拟对象框架(例如Rhino Mocks和TypeMock Isolator),这个框架更易于学习和使用。
具体来看,Moq框架中充分利用了VB.NET和C#语言的最新特征,例如lambda表达式与泛型的概念。因此,当你使用Moq创建模拟对象时,你可以使用lambda表达式来描述你想要模拟的方法与属性等。由于提供了对于最新的lambda表达式的支持,Moq得以能够提供一种格外清晰的语法形式来描述期望值、参数约束和返回值等。
根据Moq的作者的说法,你可以把“Moq”发音为“Mock-You”或者干脆与“Mock”一致。名称“Moq”源于Mock和Linq的组合。既然Moq使用的是lambda表达式而不是Linq表达式,所以名字“Mambda”似乎更为准确一些。
Moq框架的维护者为Daniel Cazzulino。你可以阅读Daniel Cazzuiono关于Moq的博客入口: http://www.clariusconsulting.net/blogs/kzu/archive/category/1062.aspx。
本文中我们集中讨论的是Moq的2.5版本,其实这并不是最新的(例如下文图中给出的2.5.3版本下载后的文件名字),但是基本代表了这个框架的最新特征。
关于模拟框架的使用,一直以来存在一些争论。目前在.NET平台上主要存在三个流行的Mock框架,它们分别是:Typemock Isolator,Rhino Mocks和Moq。其中,TypeMock Isolator因其功能过于强大而受到批评;相反地,Moq也因过于简单也受到同样的批评。为什么会存在这种看似荒诞的批评呢?
一些人批评Typemock Isolator的功能过于强大。批评者认为,TypeMock Isolator可能会导致鼓励糟糕的软件设计,因为你有可能使你在代码框架的设计方面产生惰性,理由是编写可模拟的代码应该能够迫使开发者编写出更良好的代码。现在的情形是,Typemock Isolator能够支持你模拟出任何东西,甚至是你想模拟的一切。因此,批评者认为,这个工具并没有把开发者引导到良构代码的编写道路上。他们甚至还打了另一个比方,说Typemock Isolator就像把一个核武器交给一个仅仅四岁的小孩使用一样。
在Typemock Isolator被批评其功能过于强大的同时,Moq则被批评其功能过于简单。抱怨者认为,Moq混淆了模拟与代理。如果你想深入理解上述批评观点,必须具备一定的背景知识才行。为此,你不妨参考驱动开发权威Martin Fowler曾写过一篇标题为“Mocks Aren't Stubs”的很有影响的论文,其相关网址为: http://martinfowler.com/articles/mocksArentStubs.html。
Fowler在上面这篇论文中对好几组概念加以区别。首先,他区别了代理(stub)和模拟(mock)。根据Fowler的看法(此处他引用了Meszaros的定义),代理对于测试期间进行的调用提供了封装现成的答案,经常情况下,这种封装与当前测试之外的内容是毫无关系的。相比之下,模拟(Mocks)是一些提前编写的带有一定期望的对象,这些期望对于所进行的调用加以声明-期望达到的结果。
根据上面这对概念的区别,Fowler又进一步对状态校验(state verification)和行为校验(behavior. verification)加以区别。当执行状态校验时,代理最为常用。当执行状态校验时,你更感兴趣的是测试结束某个条件是真还是假。而另一方面,当执行行为校验时,最为常用的却是模拟(Mocks)。当执行行为校验时,你更感兴趣的是模拟对象之间是如何交互的。例如,你想知道在另一个模拟对象上调用了一个方法之后是否在某个模拟对象上调用了某个特定的方法。
Fowler最后区别了传统的测试驱动开发(classical TDD)和模拟主义测试驱动开发(mockist TDD)。其实,Fowler对这两者的区别涉及到如何进行测试和设计的根本问题。传统的测试驱动开发者趋向于使用代理和状态校验。根据Fowler的说法,传统式测试驱动开发风格在于尽可能使用真正的对象,而当这种情形相当难以实现时又引入近乎两倍的真正对象以试图实现既定测试目的。而一个模拟主义测试驱动开发者几乎总是使用模拟和行为校验方案,他们会一直针对感兴趣的行为使用任何的模拟对象。
一个传统型的测试驱动程序员会使用一个模拟对象框架来简化单元测试。如果你想要测试的代码依赖于另外其他的对象,那么,你可以为当前不太方便的对象快速创建一个代理(stub),从而继续进行你的单元测试工作。因此,从一个传统型的测试驱动程序员角度来看,一个模拟对象框架的主要意义正在于此。
相比之下,模拟主义测试驱动开发者持有完全不同的观点。他们认为,应用程序的设计应该受你期望对象在程序中交互的方式的驱动。因此,他们使用模拟对象来模拟实现最终的应用程序。因此,在测试期间,他们会模拟所有的东西。
Moq因其没有尊重传统型测试驱动开发者和习惯于使用模拟对象进行测试驱动开发的开发者而受到指责。Moq使得很容易地创建代理(stubs)而不是模拟。这或者是一件好事,也可能是一件糟糕的事情,具体要依赖于你使用的模拟对象框架的目标。
Daniel Cazzulino,作为Moq框架的主要创建者,无可辩解地是一位传统型测试驱动开发者。在Daniel Cazzulino和Ayende (Rhino Mocks框架的作者)之间曾经有一次很有意思的意见交流,此网址为: http://www.ayende.com/Blog/archive/2007/12/19/Moq-Mocking-in-C-3.0.aspx
Daniel Cazzulino认为,大多数的开发者,如果他们使用了测试驱动开发的话,并不会区别什么是传统的测试驱动开发(classical TDD)和什么是模拟主义测试驱动开发(mockist TDD)。事实上,这两者之间的区别并不像Martin Fowler所描述的那样清晰分明。为此,Daniel Cazzulino撰写了一篇论文,题目为“Mocks, Stubs and Fakes: it's a continuum”,其博客地址为: http://www.clariusconsulting.net/blogs/kzu/archive/2007/12/21/47152.aspx
最终,Daniel Cazzulino没有使用Fowler/Meszaros针对模拟和代理所作出的区别。他呼吁,大多数开发者当引用Fowler/Meszaros所指的“代理”时应该使用“模拟”代之,并且强调大家应当尊重这些词语的日常语言使用习惯。
由于本文后面的内容将介绍Moq的用法,所以,我们也根据Daniel Cazzulino的做法把模拟和代理合并到一起使用。
Moq是作为Google代码工程的形式维护的。你可以从网址http://code.google.com/p/moq/处下载Moq的二进制形式及相关API参考文档。
在下载Moq的二进制形式Moq.2.5.3-bin.zip并解压后,你将得到如下图所示的几个文件。
注意到,上图中包含了一个程序集Moq.dll。当在Visual Studio中测试你的项目前,你必须添加对于此程序集的引用。当然,你还需要添加对于Moq命名空间的具体引用,以便可以使用其中提供的测试类。
另外请注意:解压结果中还一并提供了一个帮助文件Moq.chm。这是Windows平台上流行的帮助文件格式。通过此文件,你可以详细研究这个模拟对象框架的使用。
在接下来的文章中,我将详细介绍如何使用Moq创建模拟对象并编写一个简单的实例。