在前面一部分(C++单元测试一:并非看上去那么简单——几个很实际的问题),我遇到的问题是:一个单元测试工程只能测一个被测类(其实前文的后记部分也已指出,其实创建新工程也不是特别必要,可以让Mock类从被测类继承,但问题是这是真的Mock吗?);
那么,是不是一个测试工程只能测一个类呢?还是这种方式本身就有问题呢?由于没有找到实际工程中测试代码、被测代码的文件组织方面的资料,我在Google Code上下载了几个都带有完整Unit Test的项目下来,看看他们是怎么做UT的。从那几个工程来看,他们的方式有如下两种:
看了这些Google Code上的工程的单元测试,我不仅问自己,我真的需要为每一个被测类所依赖的对象都建立Mock吗?带着这个问题,找到了stackoverflow上的这篇讨论:
http://stackoverflow.com/questions/38181/when-should-i-mock
这个帖子问的就是“我何时该Mock”。有人回答说要为每一个所依赖的对象都进行Mock,否则你做的不是单元测试,而是集成测试;也有人说这种方式太过激进和理想化。
我想首先我们要理一下,单元测试所说的“单元”到底是指什么?一个方法?一个类?还是一个DLL/LIB?一个包?关于这个“单元”之所指,不同的资料有不同的说法,甚至这些说法相互冲突;维基百科给出的说法是:在过程语言里面,这个单元可以是一个完整的模块,但通常被认为是一个方法(Function or Procedure);在面向对象的语言里面,这个单元通常指一个接口,如一个类,也可能是一个方法;
好吧,我承认,我比较倾向于一个单元就是一个类这个概念,因此,上面那个帖子中的回答:如果不对类所依赖的对象进行Mock,那就是集成测试的说法,我也必须接受;但问题是,我们是否真的要为每一个依赖都进行Mock吗?这篇文章(http://media.pragprog.com/articles/may_02_mock.pdf)列出了我们何时应该Mock的几种场景:
其实,归结起来,其深层的含义应该是:当你真正想把被测代码隔离开了的时候,才进行Mock。
好吧,我们回过头来,看看我们在测ChatRoom的时候,是否真的需要为Message做Mock,针对上述的Checklist,我们的回答是:
Ok,看过上面的回答,我确信,我也不太需要为Message做一个Mock了。于是整个世界干净了,我可以在一个工程里面做很多事儿了。
好吧,我们退一步,假设真的要为Message类做Mock,该怎么做呢?
方法之一是就是从Message继承一个MockMessage出来,重新定义感兴趣的方法;这个“Mock Object Patterns"中提到的self shunt多少有点类似。
方法之二就是某些人常说的“对接口编程”(个人感觉这个概念已经被妖魔化了,好像啥子事儿经接口转一到手,就不是事儿了),于是我要为Message定义个接口IMessage(和苹果没啥子关系哈)出来,让ChatRoom依赖于IMessage;那么创建Message的Mock类并注入到ChatRoom中就很简单了。可是,这里面有几个问题:
Mocks may lead to
interface
overuseA possibleside effect of mock abuse is the unnecessary creation of Java
interface
s, for the sole purpose of mock creation (trying to avoid the problems related to class-based mocks.) Typical examples include creation ofinterface
s that will have one and only one implementation, such as utility or helper classes.This practice is often justified by a misinterpretation of the principle " Program to an interface, not an implementation." This principle refers to the concept of interface, a supertype used to exploit polymorphism, not the Java constructinterface
. It is possible to program to an interface, implemented using a Javainterface
or an abstract class.Creating
interface
s to aidmock testing increases maintenance costs (because there is more code to maintain),which usually outweighs any benefit that mocks may bring.
必须承认,Mock是一个很有效的方法,他可以将你的测试和外部世界隔离开来,将问题圈在一个很小的范围内,从而帮助你发现潜在的问题;此外,他也可以帮助我们思考如何做设计,例如当所写的代码的可测试性(在不同粒度上的可测试性,单元测试、集成测试等)不佳,那也许就要考虑一下你的设计是否有问题;
然而,Mock不是测试的万能钥匙,他也只是服务于单元测试的一种技术,更何况单元测试,还只是测试工作中的一个很小的部分;
Mock有力量,Mock要慎用;否则,单元测试的可观投入,不一定会有可观的产出。