C++单元测试二:何时Mock及其是与非

什么时候需要mock

在前面一部分(C++单元测试一:并非看上去那么简单——几个很实际的问题),我遇到的问题是:一个单元测试工程只能测一个被测类(其实前文的后记部分也已指出,其实创建新工程也不是特别必要,可以让Mock类从被测类继承,但问题是这是真的Mock吗?);

那么,是不是一个测试工程只能测一个类呢?还是这种方式本身就有问题呢?由于没有找到实际工程中测试代码、被测代码的文件组织方面的资料,我在Google Code上下载了几个都带有完整Unit Test的项目下来,看看他们是怎么做UT的。从那几个工程来看,他们的方式有如下两种:

  • 建立多个小的测试工程;这种方式和我前面的这种方式一样,整个单元测试包含多个测试工程,每个工程测试一个被测类,每个工程编译成test_XXX.exe,然后有一个Runner会在某个目录搜索,并执行所有test开头的exe,从而完成测试;但是这些工程里面也很少看到Mock代码。
  • 整个单元测试代码,放置在一个工程里面,将被测代码也引入测试工程一起编译;如mango,同样,它的测试也只看到几个Mock而已,和整个被测的代码比较起来,只能算是冰山一角。

看了这些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的几种场景:

  1.  The real object has nondeterministic behaviour. (真实对象有着不确定的行为) 
  2.  The real object is difficult to set up. (真实对象很难创建)  
  3.  The real object has behaviour that is hard totrigger (for example, a network error). (真实对象的行为很难触发)  
  4.  The real object is slow. (真实对象响应迟缓)  
  5.  The real object has (or is) a user interface. (真实对象是用户界面)  
  6.  The test needs to ask the real object about howit was used (for example, a test might need to check to see that a callbackfunction was actually called). (真实对象使用了回调机制)  
  7.  The realobject does not yet exist(真实对象尚不存在)  

其实,归结起来,其深层的含义应该是:当你真正想把被测代码隔离开了的时候,才进行Mock。

好吧,我们回过头来,看看我们在测ChatRoom的时候,是否真的需要为Message做Mock,针对上述的Checklist,我们的回答是:

  1.  No,Message的行为很确定;
  2.  No,Message很容易创建;
  3.  No,其行为很容易模拟
  4.  No,Message没什么特别耗时的操作;
  5.  No,它才不是UI呢
  6.  No,没有任何回调;
  7.  No,当然已经存在了,否则哪来的冲突。

Ok,看过上面的回答,我确信,我也不太需要为Message做一个Mock了。于是整个世界干净了,我可以在一个工程里面做很多事儿了。

Mock致接口泛滥

好吧,我们退一步,假设真的要为Message类做Mock,该怎么做呢?

方法之一是就是从Message继承一个MockMessage出来,重新定义感兴趣的方法;这个“Mock Object Patterns"中提到的self shunt多少有点类似。

方法之二就是某些人常说的“对接口编程”(个人感觉这个概念已经被妖魔化了,好像啥子事儿经接口转一到手,就不是事儿了),于是我要为Message定义个接口IMessage(和苹果没啥子关系哈)出来,让ChatRoom依赖于IMessage;那么创建Message的Mock类并注入到ChatRoom中就很简单了。可是,这里面有几个问题:

  1.   Message类本身并没有打算做扩展,强制定义虚函数纯属冤枉;
  2.   Message本身还要存放数据,鉴于“一般不要在接口中放置数据成员”,让IMessage情何以堪;
  3.  假设真要为每一个依赖对象,都做Mock处理;而要做Mock,设计时就要定义很多契约,也即接口,这会否导致接口满天飞?接口泛滥?这篇文章(http://www.oracle.com/technetwork/articles/entarch/mock-shortcomings2-096727.html)就指出Mock的缺点之一就是会导致接口泛滥,然而却冠以“对接口编程,而非对实现编程”的高帽子,这纯属对“对接口编程”的误解;原文如下:

Mocks may lead to interface overuse

A possibleside effect of mock abuse is the unnecessary creation of Java interfaces, for the sole purpose of mock creation (trying to avoid the problems related to class-based mocks.) Typical examples include creation of interfaces 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 construct interface. It is possible to program to an interface, implemented using a Java interface or an abstract class.

Creating interfaces 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有力量,Mock要慎用;否则,单元测试的可观投入,不一定会有可观的产出。

你可能感兴趣的:(测试相关)