编写单元测试的良好准则
为何需要个编写准则?
单元测试比实际实现可能还要难一些,它强迫你考虑清楚一些事情。
但单元测试本身应该简单、直接、易用和易于维护。
还要知道何时停止写测试并且开始写实现。
使用这个原则能够确保有效测试且达到目标,帮助避免一些明显的错误。
记住,编写糟糕的测试是在浪费时间,并会在以后造成更大的问题。
以下是一些良好的单元测试准则:
- 一个测试类只对应一个被测类。当前的测试类应该与其他的测试类、环境设置等没有任何依赖。
- 测试类的目录结构和被测试类的对应,这样便于快速找到错误位置。
- 一个测试方法只测试一个方法。同时,确保不要测试私有方法,它们是被封装起来的,并不是API。
- 测试用例的变量和方法都要有明确的含义。比如,将预期结果保存到
$expectedFoo
变量而不是只保存到$foo
。如果要测试很多复合结果,可使用组合变量名称诸如:$inputValue_NotNull
,$inputValue_ZeroData
,$inputValue_PastDate
等等(这要看你的代码规范约定)。 - 测试用例具备可读性。测试用例代码应该遵循规范,像应用代码一样易于理解。以后的维护者会在阅读实现前去阅读你的测试,这将帮助他们在调试前理解被测类的逻辑。
- 测试用例干净整洁。程序中不要有流程控制语句(
switch
,if
等)。一个好的测试用例处理顺序简单直接,准备数据,验证结果顺序,如有必要,使用子方法分解结构,让测试用例更加易读。如果是多场景,使用多个方法测试;例如,一个测试用例的方法代码长度最多满屏,不要有滚动条,大概在 1 到 20 行左右。如果测试代码太长,考虑拆开成多个方法,以避免混在一起相互干扰; - 测试用例要验证预期的异常。PHP中使用
@expectedException
,不要忽略它们。 - 测试用例不要连接数据库。如果测试中需要连接数据库,那么每个新的测试方法都应自主引导到临时数据库(使用
Setup
/Teardown
做准备)。如果不是必须连接数据库,请使用mock
产生确定的数据。 - 测试用例不要连接网络资源。测试某个方法时无法确保第三方的有效性,诸如网络和设备的有效性,而应该使用
mocks
代替。 - 不要在自己的类中测试第三方的类库。类库应该由它们自己的测试用例来测试,这也是我们选择类库的原因。如果它们自己没有,应该考虑使用
mock
来模拟类库的输出结果,确保输入数据的确定性。我们不应该在测试自己类功能的时候,还要考虑第三方库的的功能。 - 测试用例要处理好边界情况,极限值 (max, min) 和null变量(即使抛异常)。你要确保这些问题状况永远都不会发生,甚至在维护时不使用测试用例。
- 测试用例在任何情况下都可运行,并且不需要配置和人工干预。
- 测试用例通过当前测试,并且易于改进。测试用例要能够支持代码的演变,如果很难维护或者代码太轻而不能细化,那就成了负担(很多人不写单元测试用例就是这个原因)。
- 测试用例的输入要具体。在PHP中,测试的方法不要使用
time()
作为输入,最好使用date_format()
创建具体格式的时间。再如:name = "Smith";
不要用name = "name"
或者name = "test";
- 测试用例不要使用
@ignored
或者被注释掉,切记切记。 - 测试用例帮忙验证代码架构。如果你不能测试某个方法或者类,那么你的设计就不够灵活。
- 测试用例可以运行在任何平台,而不仅是指定目标平台。不要指望一个特定设备或者硬件配置。不然你的测试用例会迁移困难,这样会导致你会禁用他们。不应该出现“在我的机器上没问题啊”这种情况。
- 测试用例运行速度快。慢的测试会把你拖垮,快的速度会鼓励你经常运行他们,它还能帮助你减少持续集成系统上的构建时间。慎用
delay()
或者sleep()
,比如只有在某些边缘情况下,比如等待通知或者基于时钟的方法; - 把断言从逻辑中分离出来。断言应该用来检验结果,不应该执行逻辑操作的。
- 测试类不要包含私有的方法。私有方法都是一些具体的实现,不应该包含在单元测试里。
- 一个测试不要超过一个模拟(mock对象)。不然如何消除错误和不一致性。
- 保持你的测试是幂等的。测试程序应该能运行多次保持结果一致,不论运行一次还是一百万次,它的效果都应该是一样的。并且,在测试过程中,我们不应该改变任何的数据或者添加任何东西。
参考资料:
上一篇: 单元测试原理简析