编写单元测试的良好准则
为何需要个编写准则?
单元测试比实际实现可能还要难一些,它强迫你考虑清楚一些事情。
但单元测试本身应该简单、直接、易用和易于维护。
还要知道何时停止写测试并且开始写实现。
使用这个原则能够确保有效测试且达到目标,帮助避免一些明显的错误。
记住,编写糟糕的测试是在浪费时间,并会在以后造成更大的问题。
以下是一些良好的单元测试准则:
- 一个测试类只对应一个被测类。当前的测试类应该与其他的测试类、环境设置等没有任何依赖。
- 测试类的目录结构和被测试类的对应,这样便于快速找到错误位置。
- 一个测试方法只测试一个方法。同时,确保不要测试私有方法,它们是被封装起来的,并不是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对象)。不然如何消除错误和不一致性。
- 保持你的测试是幂等的。测试程序应该能运行多次保持结果一致,不论运行一次还是一百万次,它的效果都应该是一样的。并且,在测试过程中,我们不应该改变任何的数据或者添加任何东西。
参考资料:
- 19条技巧教你更好的编写单元测试:
- 单元测试原则清单