理解单元测试(Unit Testing)
本文的目的是以最精炼的语言,正解什么是单元测试,为什么要单元测试,和如何进行单元测试。
什么是单元测试(Unit Testing)?
测试(Testing)这个词很容易理解,那么什么是单元(Unit)呢?一个单元指的是应用程序中可测试的最小的一组源代码。一组源代码可测试,一般要求其有明确的输入和输出。因此,一般来讲,源代码中包含明确的输入和输出的每一个方法被认为是一个可测试的单元。注意,这里指的输出,并不局限于方法的返回值或对输入参数的改变,而包括了方法的执行过程中,改变的任何数据。
为什么要做单元测试?
单元测试的目的,是将应用程序的所有源代码,隔离成最小的可测试的单元,保证每个单元的正确性。理想情况下,如果每个单元都能保证正确,就能保证应用程序整体相当程度的正确性。
另一方面,单元测试也是一种特殊类型的文档,相对于书面的文档,测试脚本本身往往就是对被测试代码的实际的使用代码,对于帮助开发人员理解被测试单元的使用是相当有帮助的。
如何进行单元测试?
隔离
要进行单元测试,首先就是要将被测试的单元,与所有外部依赖进行隔离。一般来讲,进行隔离主要有两种可选的技术,一种叫Stub,一种叫Mock。对于两个的区别和取舍,Martin Fowler有一个很经典的文章:Mocks Aren't Stubs。简单来说,如果要Stub或Mock一个方法,Stub指的是,为了测试,手动写一个相同签名的方法,根据我的测试需要,对给定的输入参数,返回一定的输出;而Mock则借助一定的框架组件,自动生成一个方法的实现,对于给定的输入,返回一定的输出。当然,所谓的Stub或Mock,并不单指对方法,尤其是在面向对象编程中,也可以是对属性,对对象或者对接口,等等。
为了方便实现隔离,对软件设计和开发的一个潜在的激励是,软件模块的设计人员和开发人员,不得不时时思考,在当前语言支持的各种特性的条件下,如何使得所写的代码,能够被方便的被隔离。虽然这看起来很像是一种限制,但是和软件设计的SOLID原则,其实是不谋而合的,因此,也就未尝不是一个优点了。
测试
解决了隔离的问题,我们就可以关注对一个单元的测试本身了。首先要提的一点是,所谓测试一个单元,一般往往是写一段测试脚本,给定输入,验证输出。但是,其实并非所有的单元测试都可以完全由计算机方便地自动完成的。尤其是如图形UI这样的比较抽象元素的判断。因此,从相对广义的角度来说,凡是能够用于验证一个被测试的单元正确性的所有方法进行的测试,都可以被认为是有效的测试。
自动化
解决了隔离和测试本身的问题,下一个需要讨论的就是测试的效率的问题。尽管上面提到了,所谓单元测试,不完全都可以由计算机方便地自动完成,但是,对于那些可以由计算机自动完成的单元测试,如何提高写测试代码和运行测试的效率呢?答案是,陆续出现了很多的测试框架和测试工具。这些测试框架或工具,一般都会提供提高创建测试脚本效率的工具,并且,提供自动化执行测试和查看执行结果的功能,往往还可以很方便的和代码的自动化编译和部署进行集成,使测试自动化。
Guidelines
这里整理了一些如何写好测试代码的Guidelines:
- 有测试总比没测试好
- 测试尽可能的小而快
- 使测试自动化
- 进行代码覆盖检测
- 在写任何新的实现代码或测试代码之前,先修复所有运行失败的测试
- 再简单的代码也需要测试
- 特别注意输入参数的边界案例
- 不要只测试正常流程,还要测试可选和例外流程
- 对于每一个报告的Bug,写一个对应的测试用例,方便回归测试
- 时刻注意,所有测试都通过,不代表程序就真的100%没问题,测试永远只是辅助