为增强自信心而写代码
关于
单元测试,其作用我认为更多的是增强开发者的信心,以及作为代码的执行文档(额外的效果)。也就是说,编写单元测试首先是开发者的责任,其次单元测试的粒度由程序员的自信心决定。
"老板为我的代码付报酬,而不是测试,所以,我对此的价值观是——测试越少越好,少到你对你的代码质量达到了某种自信(我觉得这种的自信标准应该要高于业内的标准,当然,这种自信也可能是种自大)。如果我的编码生涯中不会犯这种典型的错误(如:在构造函数中设了个错误的值),那我就不会测试它。我倾向于去对那些有意义的错误做测试,所以,我对一些比较复杂的条件逻辑会异常地小心。当在一个团队中,我会非常小心的测试那些会让团队容易出错的代码。"
--XP和TDD的创造者Kent Beck如是说。
由过去的经验,我认同上述的观点,并且认为不应该将单元测试作为银弹。甚至项目经理应该忘记有单元测试这回事,干脆把那当做程序员的乐趣就好了,然后该有的测试流程和规范必须要求到。
不要违反原则
单元测试需遵守的关键原则是:
1、单元测试应该是可重复的
2、单元测试应该是独立的
3、单元测试是快速执行的
以下为一个坏的例子。
部门的技术主管一直希望找到一种方法可以普遍提高开发人员的代码水平,以及减少bug和改善设计,这恰恰是单元测试所擅长的。所以我们配合持续集成的实践,在后来的项目中严格要求单元测试。
不得不说,在经过一段时间的抵触后,大家还是非常喜欢的。因为我们的产品清单上有专门的单元测试时间,并且大家也开始尝到甜头--容易犯的小错少了。
但是在我们还来不及欢呼的时候,问题出现了,而且很棘手。
我们的项目大都是关于数据存储的,比如简单的新增、复杂的查询、显示报表等等。而我们的单元测试其实就是冒烟测试,并且还是不合格的单元测试。原因是我们使用
数据库的数据作为输入,众所周知,这些数据很容易被修改,故我们的单元测试是不可能具有可重复性。另外,我们的单元测试也不是独立的,因为我们非常依赖数据存储层。
从以上的描述,你可能猜到,没错,我们的开发人员大多都很初级,经常犯得错误就是关于sql的编写,ibatis的使用语法之类的错误。也就是说他们最不自信的地方就是语法或者工具的使用。
让单元测试变成可重复的相对简单,使用dbunit之类的工具可以轻松的达到,即使这个工具有时也会出错,比如oracle的Large Objec类型就会报错。但是对数据存储层的依赖就不能避免了,因为这恰恰是我们要测试的。而相比时间而言,前两个因素又显得不那么重要了。
在项目中,我们对每个dao层的方法都写了单元测试,有的单元测试花费的时间甚至比写代码的时间要多。结果是我们的代码确实是可用的,但是时间却比想象的多得多。比如由A来编写接口,然后由另外一个人B来编写页面,然后由B调用A的接口。花费的时间=编写接口+编写页面+2*调用接口时间(这个名称不怎么好,但是在海没有发现合适的名称时,还是让我们暂时使用它吧)。调用接口时间是指两个人座到一起,A告诉B如何调用他的接口的时间,加上刚好出了问题,A为了方便直接在B的电脑上修改花费的时间。这里的时间都是两份的。
而我们的项目执行一次单元测试至少要10分钟左右,而且还会报错,因为不可重复性,有时它可以执行成功,有时它并不能。现在我们要花费的时间变成编写接口+编写页面+2*调用接口的时间+n*10分钟。
之前说了,我们要求单元测试时为了保证接口是可用的,单元测试并不是唯一的方式。假设我们启动项目(5分钟),点击页面进入页面(每次0.5分钟),然后出错查看信息,解决问题(M分钟)。相比之下,使用单元测试则是启动10分钟,出错,设置打印信息,然后启动(10分钟),再设置打印信息。直至发飙。。。
现在,你应该知道我们的痛苦了。