29、测试代码质量保障:审查与重构

测试代码质量保障:审查与重构

1. 变异测试工具现状

变异测试工具在变异创建方面取得了显著进展,当前可用的工具在字节码层面工作,避免了重新编译,而早期工具是通过更改源代码来实现的。然而,变异测试工具存在性能问题,因为它们需要多次执行测试。即使每次执行只需要几秒钟,但考虑到创建的变异体数量,这可能会累积成一个巨大的时间消耗,这使得变异测试工具难以融入正常的测试驱动开发(TDD)快速迭代周期。不过,变异测试工具仍有可能提高我们衡量测试代码质量的能力,但现在就将其视为工具包中的“下一件大事”还为时尚早。

2. 代码审查的重要性

静态代码分析器远未达到我们的期望,代码覆盖和变异测试虽有其用途,但它们并不能涵盖测试质量问题的全范围。为了获得关于代码质量的真正有价值的反馈,我们需要让另一位开发者来分析代码,这一过程就是代码审查。

代码审查对于测试代码同样重要,团队在进行代码审查时应将测试代码纳入其中。测试代码和生产代码一样重要,其质量关系到开发者未来工作的顺利程度。同时,审查测试代码发现的问题可能意味着测试本身薄弱,也可能表明生产代码质量低下,这也是审查测试代码的好处之一。

3. 三分钟快速测试代码审查

3.1 规模启发式评估

可以通过查看测试代码的以下特征来大致了解其质量:
- 导入类的数量
- 使用的测试替身数量
- 设置方法的长度
- 测试方法的长度
- 测试类的长度

虽然无法给出确切的数值来判断是否存在问题,但常识通常足以区分好坏。例如,一个测试类中使用三个测试替身可能没问题,但如果有八个,就需要引起警惕。同样,对于导入类的数量和种类,也需要根据情况判断。此外,测试方法和类的长度也可通过常识来评估。需要注意的是,这些方面出现问题可能反映出生产代码存在问题,测试代码规模不合理往往意味着被测试类的职责过多。

3.2 测试是否可运行

单元测试应该能在几秒内运行完成,三分钟足够运行所有测试。需要检查以下几点:
- 是否有构建脚本允许任何人执行测试,还是需要手动设置(如运行IDE),若需要手动设置则需关注。
- 测试完成所需的时间,如果超过20秒(此值仅供参考),可能这些不是单元测试而是集成测试。
- 测试是否真的被构建脚本选中并运行。

3.3 检查代码覆盖率

三分钟应该足够运行构建脚本并查看代码覆盖报告。在短时间内,主要查看未测试代码的空白区域,例如某些包的代码覆盖率仅为20%,而其他部分接近80%。如果没有能生成代码覆盖报告的构建脚本,这也是一个需要向同事反馈的问题。

三分钟虽然无法进行全面的测试代码审查,但足以发现一些重大问题,有总比没有好。

4. 深入审查测试代码时的关注点

4.1 易于理解

一个好的单元测试应该易于理解,但一些小问题可能会破坏其可读性,需要注意以下方面:
- 命名和变量 :测试方法的名称应能揭示其实现的特定场景,测试中使用的变量应易于理解,能明确其角色,例如区分被测系统(SUT)和协作者,以及明确变量是仅为满足被测试方法的API,还是对触发SUT或其协作者的某些行为至关重要。
- 测试方法的简洁性 :测试方法应简短且专注,只测试SUT的特定功能,避免超出简单的“安排/行动/断言”操作。
- 避免不良代码结构 :测试方法中若存在for循环或使用反射来设置测试夹具,通常可能掩盖了生产代码的缺陷,违反KISS原则的情况应引起注意。
- 测试间的依赖 :测试之间的依赖会使测试代码难以理解,在单元测试中,这种依赖的使用非常有限,若存在应引起怀疑。全局变量在多个测试方法中复用也应视为代码异味。
- 外部API调用 :调用外部API时,尤其是被调用方法有多个参数时,可能会出现可读性问题,若一眼难以理解代码,可能需要改进。
- 继承问题 :测试类的继承会降低可读性,需要关注继承的层次。
- 代码风格一致性 :代码库中不同开发者可能采用不同的风格,如排列/行动/断言模式、BDD方法、实例化测试替身的方式等,一个好的单元测试套件应保持风格一致。但统一风格需要团队成员时间来达成共识。
- 非标准解决方案 :使用自定义解决方案而不依赖测试框架提供的功能,如非标准的测试夹具设置方法或运行测试方法的方式,应引起重视。
- 测试类结构 :测试类各部分(如数据提供者、私有实用方法、设置方法等)的顺序应保持一致,否则可能需要调整。
- 重复代码 :重复代码在测试代码中的处理与生产代码有所不同,如果重复代码有助于提高测试的可读性,可以保留;但如果表明TDD周期的“重构”阶段未得到认真对待,则需要处理。
- 断言 :测试失败时应能明确原因,使用正确的断言,断言消息应清晰。如果涉及逻辑判断,可考虑将其封装在自定义匹配器类中。
- 测试替身 :测试替身是常见的可读性问题来源,需要确保使用正确的测试替身,明确执行方法的期望,清楚哪些是验证内容,哪些是存根内容。同时,要注意避免过度指定的测试,并正确使用匹配器。
- 对象创建 :测试类中测试夹具设置部分常见大量复制粘贴代码,或存在许多晦涩的私有方法相互调用以设置领域对象状态,这也是需要关注的薄弱点。

4.2 文档记录

编写良好的单元测试通常不需要文档,但有时会遇到一些未记录的情况,例如测试用例的选择不明确,可能需要添加注释或链接到缺陷跟踪问题,以确保能判断测试用例是否覆盖了所有重要场景。

4.3 所有重要场景是否得到验证

这是最重要的问题,许多测试可能只对被测试方法进行单次执行,这不足以验证其正确性,属于“快乐路径”测试,应加以改进。并发代码需要进行并发测试,否则即使代码覆盖率达到100%也不值得骄傲。

通过研究代码覆盖报告可以回答以下问题,这些问题可能揭示潜在问题:
- 是否能看到多个类中未测试代码的模式,例如通常在使用真实协作者而非测试替身时,难以模拟某些代码执行路径,导致没有对抛出的异常进行测试。
- 如果有历史测试数据(持续集成服务器应提供此类报告),观察测试数量和代码覆盖率的变化。理想情况下,测试数量应增加,代码覆盖率至少应在相同值附近波动,若出现不同情况则需进一步检查。此外,还可应用变异测试验证来查找测试中的薄弱点。

4.4 运行测试

审查代码时一定要执行被审查的测试。需要检查构建脚本,确保测试不会因某些条件(如Maven配置文件)而不运行。观察测试运行速度,如果过长或执行过程中有停顿,需要进一步回答以下问题:
- 这些是单元测试还是集成测试,通常设置应用程序上下文或数据库连接会花费时间。
- 设置方法是否使用得当,对于单元测试,要查看是否有对象可以在类之前创建而不是在每个方法之前创建。
- 是否有故意的Thread.sleep()调用。

同时,查看测试执行过程中打印的消息,如果无法跟上消息显示速度,说明存在“大嘴巴”问题。还要确保测试具有可重复性,对于多线程测试或使用随机值的测试,可能需要特别关注。

4.5 日期测试

时间相关业务逻辑的测试往往容易出错,常见问题包括:
- 查找使单元测试运行时间过长的Thread.sleep()代码结构。
- 常见错误是只测试当前日期,这意味着只有在生产代码出现问题的当天才会发现错误,应确保覆盖所有测试用例。

5. 代码审查总结

在确保测试代码质量的多种方法中,代码审查是最值得推荐的。它有助于发现缺陷,使团队在编码方式上达成一致,并在团队成员之间传播关于软件开发部分的知识。此外,通过审查测试代码可以发现生产代码的很多问题。唯一的缺点是进行全面的测试代码审查需要大量时间。强烈建议将测试代码审查纳入团队的工作常规,使其成为“完成定义”的一部分,并且像对待生产代码一样对待测试代码。

下面是一个简单的mermaid流程图,展示快速测试代码审查的流程:

graph LR
    A[开始] --> B[规模启发式评估]
    B --> C[测试是否可运行]
    C --> D[检查代码覆盖率]
    D --> E[结束]
审查方面 具体检查内容
规模启发式评估 导入类数量、测试替身数量、设置方法长度、测试方法长度、测试类长度
测试是否可运行 是否有构建脚本、测试运行时间、是否被构建脚本选中
检查代码覆盖率 查看未测试代码区域

6. 测试代码重构的原因与方法

6.1 重构的原因

测试代码非常重要,需要进行清理以使其易于理解和维护。通过对测试代码进行各种有时是微小的更改,可以让测试更清晰地传达测试意图。当一个编写良好的测试失败时,也更容易找出原因。

6.2 重构的方法

与生产代码不同,我们没有针对测试代码的测试。但可以通过以下简单建议来处理:
- 小步重构并频繁运行测试 :每次进行小的更改,并在过程中经常重新运行测试,确保没有引入新的问题。
- 借助工具辅助 :代码覆盖和变异测试等工具可以帮助判断测试的安全网是否变松。

有些人建议在开始重构测试之前,先更改被测系统(SUT)的实现,使测试失败。重构测试并重新运行后,测试仍应失败,这表明断言仍然有效。当恢复生产代码的更改后,测试应该再次通过。不过,这种方法在实际的重大重构中并非必要,小步移动通常就足够了。

6.3 重构的时机

明显的重构时机是测试驱动开发(TDD)节奏中的重构阶段,但这不是唯一的时机。实际上,每当浏览测试代码时感觉不舒服,无论代码是否是自己编写的,都应该进行重构。遵循“童子军规则”,即“让营地比你发现时更干净”,即使是小的更改,累积起来也会产生很大的影响。

7. 测试代码重构示例

虽然没有给出具体的代码示例,但可以想象一些常见的重构场景。例如:
- 使用更合适的断言 :将 assertTrue() 替换为 assertEquals() 来比较对象,提高断言的可读性。
- 使用数据提供者 :用数据提供者代替 for 循环,使测试代码更简洁。
- 优化 setUp() 方法 :创建专门的 setUp() 方法,提高测试代码的可维护性。

8. 总结与建议

8.1 总结

确保测试代码质量是软件开发过程中的重要环节。变异测试工具虽然有潜力,但目前还存在性能问题,难以完全融入快速迭代周期。代码审查是保障测试代码质量的有效方法,它不仅能发现测试代码中的问题,还能揭示生产代码的缺陷。同时,对测试代码进行适时的重构可以提高其可读性和可维护性。

8.2 建议

  • 定期进行代码审查 :将测试代码审查纳入团队的日常工作流程,确保测试代码和生产代码都得到充分的审查。
  • 合理使用工具 :利用代码覆盖和变异测试等工具辅助代码审查和重构,但不要过度依赖它们。
  • 培养重构意识 :团队成员应养成随时重构测试代码的习惯,不断提高测试代码的质量。

下面是一个mermaid流程图,展示测试代码审查与重构的整体流程:

graph LR
    A[开始] --> B[代码审查]
    B --> C{是否发现问题}
    C -- 是 --> D[测试代码重构]
    C -- 否 --> E[结束]
    D --> B
操作阶段 主要任务
代码审查 规模启发式评估、检查测试运行情况、查看代码覆盖率、关注代码可读性和场景验证等
测试代码重构 小步重构、借助工具辅助、适时进行重构

通过以上的审查和重构方法,可以有效地提高测试代码的质量,从而提升整个软件开发项目的稳定性和可靠性。在实际工作中,团队成员应密切合作,共同遵循这些原则,确保测试代码始终保持高质量状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值