找到几篇关于 java 测试的最佳实践. 无论是否真的是最佳, 至少都是高手的经验之谈.
原文链接 作者: Alvin Reyes
- TDD, 写代码前先写好对应的测试用例.
- 避免在测试代码中对其他非测试部分产生影响.
- 避免在测试用例中操作任何生产环境或其他环境的数据
- 避免写出需要人为干预的测试用例. 基于以上, 测试用例所使用的数据应该是封闭的, 每次运行前都处于明确的状态下, 以保证每次运行测试用例的效果完全一致. 测试完成后, 需要恢复测试使用的数据, 或者直接清除测试用例专用的测试数据库
- 绝不要在 Build 的时候跳过测试, 比如
-Dmaven.test.skip=true
- 要使用有意义的测试用例名称, 要能够大致描述测试的对象或行为
比如, 待测方法名称为 createOrder(), 对应测试方法名称之一可以是 testSampleServiceCreateOrder(). 此外, 断言中的描述对于测试也非常重要. - 每个测试单元中, 至少需要包含有一个断言. "经验法则 : 每一个断言, 都是一个测试"
- 好好利用 Junit 中的 Assert 及其他工具包, 如果可以联合其他相关的模式匹配工具则更好(比如 Hamcrest ).
- 在合适的位置使用合适的注解.
- 测试代码 Package 的命名和结构应该保持和实现代码的保持一致. 这样可以提高测试代码的可维护性.
- 生产环境或发布版本中不能带有测试代码, Build 工具 (比如 Maven) 一般都有相应的功能.
- 不要使用构造器来构造测试用例, 而应该使用 @Before @After 等注解.
- 不要为了通过一项测试用例, 而写一个无意义的测试, 比如仅有一句
assert(true);
- 避免在测试用例中自己写 try catch 来处理异常, 而应该使用 Junit 提供的注解声明预期产生的异常.
- 没有必要为了打印异常的详细信息而使用 try catch 来手动捕获异常, 应该交给 assert 来做异常输出.
- 如非必要, 尽量避免在测试用例中引入线程, 另外, 也不要使用 Thread.sleep() 等方式让执行暂停.
- 使用日志打印必要的信息.
- 使用 Maven 或 Gradle 等自动化 Build 工具.
- 使用工具进行测试用例覆盖度的检测, 并对测试结果生成展现形式更友好的报告. 推荐使用 surefire 插件.
- 代码的测试覆盖率应该尽可能到达 80% ( 业界经验准则 )
- 在测试中使用适当的 Mock 技术, 有很多可用的 Mock 工具. 数据和对象都可以 Mock.
- 测试代码要精简, 执行要快, 压力或负载测试应该独立出去.
以上这些 "最佳实践" 基本上让我们对单元测试有一个大致的认识, 那些是对的, 那些是错的. 但是具体怎么做呢?
原文链接 作者: Viktor Farcic
TDD, 测试驱动开发其实是一种开发模式. 定义了一种高精度高质量的开发流程:
while (有需求) {
根据需求编写测试用例
do {
编写或修正代码以通过测试
} while (测试执行失败)
do {
重构并优化代码
} while (测试执行失败)
}
原文中给出了一个使用 TDD 进行开发的实例, 很形象的描绘了 TDD 开发过程.
对于 TDD, 到底是让开发变的更敏捷还是更迟缓了?
con :
- 开发前需要将产品需求拆解成代码模块需求(单元)
- 需要针对模块需求编写测试用例, 测试代码的代码量甚至比实现代码更多
pro :
- 功能需求的拆解使得代码更加模块化, 进一步使得代码结构的设计质量更高
- 测试保证了代码的质量, Bug 更少, Debug 更快
- 是代码重构的一个前提条件, 可以保证重构后代码的逻辑正确性
- 测试代码本身就是整个项目的最好的开发文档
TDD 结合 ATDD(Acceptance Test Driven Development) 和 BDD(Behavior Driven Development) 可以更好的覆盖单元测试和功能测试, 并且完全能够充当开发文档.
原文链接 作者: Viktor Farcic
该文承接上文, 给出了 TDD 的最佳实践, 此文曾被 Hacker News 收录. 另, 该文作者著有_Test-Driven Java Development_
最佳实践是在特定情景下的一类问题的通用解决方案, 不采纳最佳实践会导致我们重新去发明轮子, 并花费时间精力去解决那些已经被解决过的问题. 但从另一个角度来说, 我们也不能盲目的采纳所谓的最佳实践, 而是应该去学习去尝试, 在完全了解它之后, 再根据自己的实际情况进行部分修改调整, 然后采纳; 或者, 如果不适合目前的状况, 就不必引入了.
最佳实践主要设计以下几个方面:
- 命名习惯
- 测试执行
- 开发实践
- 工具
(和前文大体类似的部分此处省略了, 如需要请查看原文)
遵循命名习惯可以帮助开发者更好的理解和维护代码. 另外, 很多工具都依赖一些通用的命名习惯.
- 实现代码和测试代码分离:
许多构建和测试工具都需要将测试代码放在一个特定的源码目录中. Maven 和 Gradle 默认 src/main/java 中是实现代码, 而 src/test/java 中是测试代码.
-
实现代码和测试代码的包名结构保持一致:
-
测试类的命名和实现类命名有规律:
通常是用 Test 或其他类似词汇作为前缀或后缀. 如果实现类比较复杂, 可以按照功能模块将测试类分割成几个模块.
使用 Mock 技术: 可以降低测试的相互依赖性, 更快速的编写测试用例.
一般的 Mock 对象包括: 数据库, 其他产品和服务等.
避免在测试代码中使用基类, 即尽量不要有过多的继承层次. 目的是保持测试代码的简洁易读, 无依赖性.
覆盖率: JaCoCo Clover Cobertura 持续继承: Jenkins Hudson Travis Bamboo
TDD 更适合快速进行高质量的开发, 用于进行代码的测试覆盖. TDD 是白盒测试
BDD 更适合最终根据功能需求进行整合测试. 常用于进行最终的自动化验收测试. BDD 是黑盒测试