论坛首页 综合技术论坛

再论要不要全程MockObject

浏览 30584 次
该帖已经被评为精华帖
作者 正文
   发表时间:2006-04-22  
MockObject的好处:
第一、隔绝其他模块出错引起本模块的测试错误。
第二、隔绝其他模块的开发状态,只要定义了接口,不用管隔壁那条友开发完成没有,debug干净没有。
第三、一些速度较慢的操作,可以用mockObject代替,快速返回。

   但MockObject也有不爽的地方,所以,我的观点仍然是,团队应该以自己的开发顺序,人员与模块划分,决定某个TestCase是否使用MockObject。其实和Every Class should be Interface Base一样,大牛们推荐的是最好的东西,但我们仍然要以Pragmatic的批判精神,按实际情况选用。

   MockObject不爽的地方:
  第一是事件录制的代码量相当大,而且干扰TestCase的可读性。比如函数返回一个Book,我就要手工设置这个Book所有测试相关的属性,如果返回的是Book List,那就更加.....如果分支很多,你还要不断重复录制....比如它如果有嵌套的函数调用,还要一个个录下去....看到眼花时,还很干扰你的测试校验代码。

    第二是代码重复的bad smell,比如ApplicationContext.xml定义好了的对象管理关系,Test里又手工设置了一次。
   更重要是本来bookManager的sell方法,无论是接口和行为定义都只有一个地方,引入mockObject后,每一个录制mockObject的地方,就多了一个对该函数接口和行为的定义,代码修改和重构时很麻烦(注意,这和unit test对重构的支持是两个不同的概念)

   因此,如果强制全程推行的话,代码量那是相当壮观,lighter,faster java的口号也白喊了,什么lighweight框架省下的代码还不够ut里填的。 1行代码,10行测试对于国内大部分资源少,时间紧的团队来说还是太奢侈了。

  而且,ut很重要,但ut不能代替集成测试,所以最终还是要有no mock object的集成测试test case
   发表时间:2006-04-22  
复杂的话就用stub替换mock,收效会比较大
0 请登录后投票
   发表时间:2006-04-22  
引用
第一是事件录制的代码量相当大,而且干扰TestCase的可读性。比如函数返回一个Book,我就要手工设置这个Book所有测试相关的属性,如果返回的是Book List,那就更加.....如果分支很多,你还要不断重复录制....比如它如果有嵌套的函数调用,还要一个个录下去....看到眼花时,还很干扰你的测试校验代码。


做单元测试的时候,准备的测试数据应该是符合条件最简化的形式,只准备最必须的最少最简单的数据形式。例如测试一个Book是否被load,你只需要放一个book.name就足够了,无需准备更多的数据。如果是Book List,你仅仅需要准备两个book对象足够了。这样算下来,其实测试数据的准备量并不大,至少我写的单元测试里面,我感觉这部分工作量实在不算什么,比起来真实环境跑起来的时候那些复杂的数据,真的是太简单的工作了。

不管你用不用mock对象,如果功能代码的分支流程很多的话,你必须要为每个分支单独写一个testXXX去测试它,否则你的单元测试就是不完备的,这和mock不mock没有任何关系。

至于说嵌套很深的调用,就一定说明你的设计有问题。对象调用应该遵循迪米特法则,只调用触手可及的对象。

引用
第二是代码重复的bad smell,比如ApplicationContext.xml定义好了的对象管理关系,Test里又手工设置了一次。
更重要是本来bookManager的sell方法,无论是接口和行为定义都只有一个地方,引入mockObject后,每一个录制mockObject 的地方,就多了一个对该函数接口和行为的定义,代码修改和重构时很麻烦(注意,这和unit test对重构的支持是两个不同的概念)

首先你一个bean会依赖很多其他bean吗(如果依赖过多bean,就要审视一下是否是设计的问题)?其次在一个testcase里面,你给一个bean set几个mock对象是很麻烦的事情吗?这叫做代码重复的bad smell?

你的功能代码里面,其他的bean调用了bookManager的sell方法算不算对该函数接口和行为的定义?你代码修改和重构的时候,难道你都不需要去关注别的功能代码对该函数的调用吗?

在强调针对接口编程的情况下,无论是功能代码对该接口的调用bookManager.sell(...)(接口实现类通过spring容器注入),还是Test代码对该接口的调用bookManager.sell(...)(接口实现类是Mock对象),有什么区别呢?

引用
因此,如果强制全程推行的话,代码量那是相当壮观,lighter,faster java的口号也白喊了,什么lighweight框架省下的代码还不够ut里填的。 1行代码,10行测试对于国内大部分资源少,时间紧的团队来说还是太奢侈了。


如果功能代码的分支流程比较多,你确实可能需要写非常长的测试代码去覆盖每个流程,上周末BEA SHUG,我请教过红工场Charles Huang,他告诉我,测试代码是功能代码的两三倍都是很正常的事情。不能因为测试代码量大就拒绝进行完备的单元测试编写。因为越是项目后期,有一个完备的单元测试集,你就对项目代码的信心越足。特别是项目代码树已经非常庞大的情况下,你需要对底层架构进行比较大的调整的时候,单元测试集是你成功的保证,当你完成底层架构的调整之后,看到几百个test全部green bar的时候,心情之爽是难以言表的。

写完备的test case,开始看起来似乎拖慢了速度,但是他确实可以保证软件开发整个过程以稳健的速度前进,而且控制的了风险。特别是对那些需求非常模糊和变动非常剧烈的项目,在整个开发过程中,不断调整底层框架是非常频繁的事情,当你的整个codebase变得比较大的时候,如果没有完备的单元测试,你是绝对没有信心去调整的,而不断妥协的结果就是codebase越来越腐烂,最后变得无法继续开发,推倒重来。其实我现在越来越感觉到,写单元测试是程序员对自己负责任的行为。如果你不想项目后期频繁加班,如果你不想面对一个无法下手的项目,如果你不想痛苦的维护一个很烂的codebase,如果你希望心情愉快,每天按时上下班的话,写完备的单元测试就是达成你快乐工作的前提。

另外写单元测试也会强迫你编写出来高质量的功能代码。因为低劣的功能代码是极难测试或者根本不可测试的。而只有始终保持高质量的功能代码,你才会对项目的周期和质量有信心,而对于每个有追求的程序员来说,面对一个高质量代码的项目,无论如何都是心情很愉快的事情。

虽然我不喜欢像一些人一样总是推荐别人读这本书,那本书什么的。不过你的这些疑问在without EJB第14章单元测试里面都有非常明确的分析和解答。另外也非常值得看一下Kent Beck的《TDD by example》,很多问题里面都有解答。
2 请登录后投票
   发表时间:2006-04-22  
用matin的术语来说, 这里有两种单元测试方式: state-based tests;interaction-based tests(就是用mock这种方式). 两者各有利弊:
test-isolation: interaction-based tests is better than state-based tests
coupling to implementations:interaction-based tests are more coupled
to implementation details.

具体去可以去看matin的这篇文章http://www.martinfowler.com/articles/mocksArentStubs.html
1 请登录后投票
   发表时间:2006-04-22  
hehe,对robbin大佬的话大体表示同意。因为我这种每人天300行代码的团队还达不到两三倍测试代码的幸福水平,所以对全面实行mock object后的好处和坏处都只是空想。

不过对代码重复一项保留自己的意见,sean也说martin说了,coupling to implementations:interaction-based tests are more coupled to implementation details.

在实际代码改动时,一修改ut就会产生这个感觉,以前只关心对object的使用者的影响,现在多了一堆mockobject的接口和行为的二次定义。
0 请登录后投票
   发表时间:2006-04-22  
江南白衣 写道
hehe,对robbin大佬的话大体表示同意,只对代码重复一项保留意见

sean也说martin说了,coupling to implementations:interaction-based tests are more coupled to implementation details.

你在代码改动时,修改一把ut就会发现这个问题,如果使用了mock object,代码改动时,ut里的修改量大了很多,要修改所有的mock object接口及其事件录制。


功能代码和testcase都是针对接口的调用,如果你修改接口契约,难道你不需要修改所有的功能代码对这个接口的调用和参数传递吗?

mock实际上就是在模拟功能代码对这个接口的调用情况,当你修改接口契约以后,你对mock事件录制的修改其实就相当于你在功能代码中对接口调用参数的修改,所以mock这里起到了非常强的检查机制。如果修改接口契约,你却不需要修改mock的事件录制,你能保证功能代码对这个接口调用就一定不会出问题?
1 请登录后投票
   发表时间:2006-04-22  
哈哈,一言以蔽之,就是懒阿。

通过事件录制是可以做到很好的契约检查,不过考虑到工作量就有点不爽了。特别像用旧版easymock和jmock,会死人的。一些heavy的软件工程方法听起来也很不错的,但执行的程序员就是懒阿,能有可接受的测试效果就算了,呵呵。
0 请登录后投票
   发表时间:2006-04-22  
引用
现在多了一堆mockobject的接口和行为的二次定义


没有明白什么意思,写段代码解释一下?


另外我想补充说明一点,就是mock也罢,stub也好,其目的都是要做到test能够完整的覆盖功能代码,这是最终的目标。至于具体的选择,就要看个人的偏好了。

我个人经过考察,觉得easymock2.1是其中代价最小的方案,当然Rod Johnson的推荐和springframework多达60%的test都是用easymock去mock的,也是很强的理由。
0 请登录后投票
   发表时间:2006-04-22  
江南白衣 写道
哈哈,一言以蔽之,就是懒阿。

通过事件录制是可以做到很好的契约检查,不过考虑到工作量就有点不爽了。特别像用旧版easymock和jmock,会死人的。一些heavy的软件工程方法听起来也很不错的,但执行的程序员就是懒阿,能有可接受的测试效果就算了,呵呵。


这个我还是颇有点体会的。通过easymock的verify方法的检查机制,我发现了好几次我没有测试到的流程,或者假设错误的流程,所以我觉得测试覆盖率很重要。

懒惰的问题,我觉得只有TDD能够解决了。如果先写测试,再写功能代码这种驱动编程方式,就不会出现这种现象。如果先写功能代码,再写测试,就一定会出现测试偷工减料的现象。

当然我自己还无法做到TDD,我一般是功能代码和测试一起写的,写一点,测一点。如果是TDD的话,那就是测一点,写一点了。而且TDD做的好的话,思维是不需要考虑那么远的,只能看到眼前那一点点功能,一小步一小步的前进,目前我的思维习惯还没有完全转变过来。
0 请登录后投票
   发表时间:2006-04-22  
robbin 写道
引用
现在多了一堆mockobject的接口和行为的二次定义


没有明白什么意思,写段代码解释一下?



   原来函数的具体行为只有在真实代码里定义的。而现在需要在每次录制事件,也就是robbin讲的制定行为契约时,根据不同的上下文,每次重新定义一次。

  所谓stub,就是相对减少上下文的影响,只在全局定义一次mock Object吧。
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics