我将写单元测试代码看作开发的一个工具,就像ide起一样的作用,帮助我们开发。
写单元测试最好在第一个接口函数完成时就开始写,否则代码多了,就不原意写了。
有框架可以用框架,没框架自已写个简单的程序验证一下自已的假设即可。
我一般c++用gtest和gmock,c#用nunit,java用junit。用法都很简单。mock会麻烦一些,找个教程多看一看就差不多了。主要是对单元测试的理解。
其实很多开源项目都是写一个简单的小程序,printf出相关数据看是否与自已期望的一致。
真正写起来,单元测试代码量很少,否则说明你接口设计的有问题。这就引出单元测试代码的作用,帮助你写出好的接口。
好的接口要功能明确,职责单一,如果一个接口干多件事,你的单元测试就没法写,因为太多了。如果一个接口的单元测试函数超过四五个,就建议你好好考虑是否接口设计的有问题,对这个接口甚至整个功能做什么是否真的想清楚了。包职责是否单一,是否合理的分层。如果未合理分层,也会导致单元测试代码膨胀。
有些时候你会遇到单元测试不好写,过度依赖mock才能写测试代码,这是因为代码的耦合度高的原故,应该让代码尽量独立,低耦合。减少对其它模块或者环境的依赖。例如你在一个接口中依赖了系统时间,这个单元测试就比较难写,倒不是难写代码,而是你运行时总不能等待系统时间吧。这时就有了对系统环境的依赖,最好的办法是将时间作为参数传进去。当然用mock也可以解决难测的问题,但这不是正确的解决办法。
写单元测试代码还有助于你写出符合开放封闭元则的代码。很简单,你一般不会将一个本来应为private的函数写成public的,因为需要测试啊。
如果你关注了接口,真正考虑好了接口,功能的定位,符合了开放封闭原则,也不大需要重构,或者说在开发阶段就已完成大部分重构了。
因为代码进行了很好的分层,以后既使功能有改动,一般情况下也不会影响所有层。
如果是写c或c++代码,你也不会多引入头文件,因为单元测试代码,很多时候要自已写makefile,你多引入了头文件,makefile会变复杂。
你也不会写超长的函数,因为超长的函数一般很难测试。
你也不会过度使用继承与虚函数,而会考虑组合,因为继承与虚函数也很难测。
其实很多时候单元测试代码比想像象中的少,写起来也比想象中简单,前提是你想明天接口要做什么,接口设计好。
但是单元测试不容易上手,我觉得主要是难以想明白:把写单元测试代码看成测试而不是开发辅助手段;什么tdd一堆的概念弄得人晕头转向;总觉得写单元测试代码很高大上,需要框架支持才能做;总觉得单元测试代码会很多,会是实际代码的好几倍;总是在代码完成后才写单元测试代码,这时发现单元测试代码根本没法写,接口设计不合理,耦合度高等;一定要团队整体提倡才好写,否则生产率低。这些想法都没必要。
千万不要等到所有代码都完成再写单元测试代码,因为这时候写单元测试代码意义已经不大,而且很多情况下,这时候已经没法写单元测试了,已完成代码很可能存在接口设计不合理,耦合度高等问题,且单元测代码量会很大,望而生畏。同理,也不要为别人写单元测试代码,也不要别人替你写单元测试代码。
其实写单元测试代码对整体时间来说不是延长了,而是缩短了,因为一般一个功能要通过,需要询试几次,包括编译(一般要全部编译),发布,手动验证,有些时候还需要配置环境,而单元测试代这些都可以自动化。而且单元测试代码已经提前协助做了重构。代码质量得到了提高。被测试人员测出的bug也会有所减少。
很多人开始考虑写单元测试代码,是觉得单元测试代码对重构有帮助,主要是指后期重构。开始也是这样理解。真正开始写了后发现,对代码的前期(所有代码完成前)帮助最大。有时我甚至会前期写单元测试代码,后期就直接忽略了(因为觉得有时候小的改动对逻辑没影响,单元测试代码也就懒得同步了),当然这不是好习惯。
还有,很多人说单元测试是白盒测试,讲求代码覆盖率。这也是很多人觉得单元测试代码难写写的原因之一。其实单元测试不应该讲代码覆盖率,而应该是功能或逻辑覆盖。某一个接口函数,是干什么的,有几种可能性,都考虑到了,写了相关的测试函数,也就可以了,不用讲代码覆盖率,不过这些覆盖了,代码也就覆盖了,否则可能有废代码。还有,从接口函数角度看,我觉得是黑盒的。而且允许里面的实现变动,只要测试函数都能通过即可。
写单元测试仅仅是个开发的辅助工具,不是非它不可,写不写都不用纠结。我的建议是早点动手实践,在实践中反思。没有真正想通,或觉得没用,就先放一放。
写单元测试也有很多限制,修改已有代码很难写单元测试代码。数据库操作,界面我不知道怎么写单元测试。
编辑于 2017-08-20
著作权归作者所有