引用
第一是事件录制的代码量相当大,而且干扰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》,很多问题里面都有解答。