21、代码设计时谨记测试
编写和维护单元测试的代价是很高的, 减少代码中的公有接口和循环复杂度是降低成本, 使高覆盖率测试代码更易于编写和维护的有效方法。
一些建议:
使类成员常量化, 在构造函数中进行初始化。 减少 setter 方法的数量。
限制过度使用继承和公有虚函数。
通过使用友元类 (C++) 或包作用域 (Java) 来减少公有接口。
避免不必要的逻辑分支。
在逻辑分支中编写尽可能少的代码。
在公有和私有接口中尽量多用异常和断言验证参数参数的有效性。
限制使用快捷函数。 对于黑箱而言, 所有方法都必须一视同仁的进行测试。 考虑以下简短的例子:
public void scale(double x0, double y0, double scaleFactor) { // scaling logic } public void scale(double x0, double y0) { scale(x0, y0, 1.0); } |
删除后者可以简化测试, 但用户代码的工作量也将略微增加。
22、不要访问预定的外部资源
单元测试代码不应该假定外部的执行环境, 以便在任何时候/任何地方都能执行。 为了向测试提供必需的资源, 这些资源应该由测试本身提供。
比如一个解析某类型文件的类, 可以把文件内容嵌入到测试代码里, 在测试的时候写入到临时文件, 测试结束再删除, 而不是从预定的地址直接读取。
23、权衡测试成本
不写单元测试的代价很高, 但是写单元测试的代价同样很高。 要在这两者之间做适当的权衡, 如果用执行覆盖率来衡量, 业界标准通常在 80% 左右。
很典型的, 读写外部资源的错误处理和异常处理就很难达到百分百的执行覆盖率。 模拟数据库在事务处理到一半时发生故障并不是办不到, 但相对于进行大范围的代码审查, 代价可能太大了。
24、合理安排测试优先次序
单元测试是典型的自底向上过程, 如果没有足够的资源测试一个系统的所有模块, 就应该先把重点放在较底层的模块。