关于单元测试

这段时间,部门的工作基本上都进入了正轨,现在需要从头开发的工作已经很少了,大多是在已有的基础上做修改。跟我直接相关的项目有三四个,经常一天要在多个项目间切换,要很快地在项目间切换状态,挺不容易的。

经常遇到的问题是,改完旧的代码后,不知道怎么测试,功能点都忘得差不多了。然后就是开始翻文档,看需求,再找到测试的url或者写测试的程序。当初写代码的时候,都是以完成需求为目标,程序小的时候,也没什么问题,后面程序大了,就有了些压力。现在做维护和修改就更没底了,很多时候,要来来回回很多次,才能通过。

近来整理了一些以前写的代码,把一些通用的东西提取成库。写开发库跟业务开发不样,代码要严谨得多,我开始看编码规范、参考别人写的库,也就是这样,我才再次注意到了单元测试。

##基础
单元测试,就是以程序中最小的逻辑单元为对象,编写测试程序,来验证逻辑正确与否。一般来说,最小的逻辑单元就是函数。单元测试就是罗列出一些输入值和对应的输出值,然后调用测试的目标代码,对比执行结果与预期的结果是否一致。

在对程序有大的改动时,单元测试尤其有用,跑一遍测试,很多容易忽视的问题就暴露出来了。

单元测试的代码也可以用作程序文档的范例,而且是最准确的范例。程序文档可能更新不及,单元测试永远不会。

##应用
单元测试的倡导者甚至提出所谓的TDD(Test-Driven Development),也就是测试驱动开发,在编写程序之前就先写好测试的代码。对于我现在的情况还是以解决旧代码的问题为主,是在已有的程序上编写测试代码。

我开始对一些程序编写测试代码,但是我发现有些程序很难写测试,有过多的外部依赖。我在网上找解决方案,得到的回答基本都是原程序代码设计不合理或者不适合单元测试。在编写单元测试,目标程序要遵守一些规范,才能更好地与测试程序配合。

因此,我只能重构原有代码,把一些复杂的函数拆分开,将有外部依赖的部分封装起来,通过一些中间层代码来避免依赖。

经过重构,不但代码可以实施单元测试,程序逻辑和流程也更加清晰易懂。

##目标程序的规范
测试的目标程序代码应避免以下几种情况:

  • 存在太多条件逻辑
  • 构造函数中做的事情太多
  • 存在太多全局状态
  • 混杂了太多无关的逻辑
  • 存在太多静态方法
  • 存在过多外部依赖

##什么时候用
思考这个问题时候,在网上浏览了一下,找到了一编不错的文章“单元测试要做多细”。尽量为下面几种代码编写单元测试:

  • 经常修改的代码
  • 程序的核心代码
  • 开发过程中,程序员没有把握,可能出错的地方或是那些边界情况
  • 维护过程中,出现过的bug,确保bug不会再出现

##接手代码
程序员最痛苦的事,莫过于接手别人的代码。当一个同事离职了,留下了一堆需要维护的代码。这个时候,怎么开始呢,先看文件,然后读代码,然后一知半解地修修补补?

其实这个时候,单元测试是一个很好的工具,如果同事的代码没有单元测试,那为它们编写单元测试会是一个了解代码的好方法。拿到代码时,先快速地了解一个结构,并大致判断可能出问题的代码块,然后依靠现有的认识编写测试程序。如果测试通过了,那说明代码跟对功能认知没有问题,反之要嘛代码有问题,要嘛理解有问题了。根据结果修改测试程序和目标程序,直到测试都通过。

这个过程的从原本"读代码-> 了解 -> 使用" 转变为"读代码 -> 使用 -> 了解。只是两个步骤顺序的互换,就带来了很不一样的效果。不但加深了对程序的理解,还记录了结果。可以在后续的修改中避免很多错误。

这个方法在学习第三方程序库的时候也可以使用。

如果你接收了一个带有完整单元测试的程序,马上给编写的人打个电话感谢他吧:D

##单元测试的问题
单元测试也不是那么完美,它能涵盖的范围也只是目标程序的一个单元,它不能发现整合错误、性能或者其他系统级别的问题。

单元测试也带来额外的维护成本,对代码重构,也要及时对测试程序做好修改。同时要做好版本控制,避免测试程序与目标程序版本不能对应的问题。