代码健康(11):测试覆盖率的最佳实践

乔梁 | 2021-04-13

我们一直提倡的是:使用代码覆盖率数据来评估风险,并识别测试与预期的差距。

然而,代码覆盖率的价值是一个备受争议且观点两极分化的话题。每次与一群工程师讨论代码覆盖率时,都好像会有无穷无尽的争论接踵而来。

假如大家都为了安全感而都躲在各自的阵营里,那么,这种对话最终都会以没有任何成效而结束。

本文目的是为你提供一些工具,以便引导各方人员找到共同点,从而能向前走,且务实地使用覆盖率信息。

我们提出了代码覆盖率领域的最佳实践,以有效地处理代码健康问题。

  • 代码覆盖率为开发工作流程提供了显著好处

“代码覆盖率”并不是一个完美的测试质量的衡量指标,但它提供了一个相对合理、客观,并具有可行动性数据的行业标准指标。它并不需要大量的人机交互,普适于所有的产品,并且在业界有足够多的工具。你必须理解的是:它是一个有损的间接指标,它将大量信息压缩成一个数字,所以它不应该是你唯一的真相来源。相反,将它与其他技术结合使用,才能对测试工作进行更全面的评估。

  • 提高代码覆盖率的努力往往带来优秀的工程文化。

单靠代码覆盖率是否能减少缺陷还是一个相对开放性的研究问题, 但经验表明,提高代码覆盖率的努力往往带来优秀工程文化的变化。从长远来看,这也会减少缺陷。例如,重视代码覆盖率的团队更倾向于将测试代码视为一等公民,并且倾向于将更强的可测试性融入到自己的软件产品设计中,这样,他们就可以使用更少的成本达成测试目标。所有这些又会反过来推动大家编写更高质量的代码( 架构更模块化、API 有更清晰的契约、更易于管理的 CodeReview 等)。他们也开始更加关心自己代码的整体健康,并追求卓越的 DevOps 。

  • 代码覆盖率数值高并不能保证高质量的代码覆盖

把注意力集中于“让覆盖率尽量保持接近 100% ”会让你产生一种错误的“安全感幻觉”。同时,它还有可能是一种浪费。例如,对机器资源造成了浪费,维护那些低价值的测试代码产生的投入也是一种浪费,因为它们算是所有技术债务中的一种。

另外,缺少测试的低质量代码被部署到生产环境中,也是有可能的。因为:( 1 )你的测试并没有覆盖到某个特定的代码路径。而这种情况是可以通过代码覆盖率分析提前发现的。或者( 2 )因为您的测试虽然覆盖了某个语句,但并没有覆盖其中的某个特定的边界条件(如多条件语句)。这种情况很难或不可能被代码行覆盖率分析所发现。

代码行覆盖率不能保证覆盖的行或分支已经被正确测试了,它仅仅是保证它们被执行过。你一定要当心下面这种行为,即:为了增加覆盖率而进行的类似“作弊”,例如:通过复制/粘贴测试代码,增加了一些实际价值很少的测试。

有一种更好的技术,可以用来评估是否充分地通过运行测试来覆盖代码行,且充分地做了断言,那就是变异测试。

  • 代码覆盖率低一定说明:每次部署时,软件产品的大部分都没有被自动化测试

代码覆盖率低,增加了低质量代码推送到生产环境而产生的风险,因此应该引起注意。事实上,代码行覆盖率数据的价值并不仅仅是突显哪些代码已经被覆盖了,而更多是为了突显还有哪些代码没有被覆盖。

  • 并没有一个“理想的代码行覆盖率数值”,可以统一应用于所有的产品

产品所应达到的代码覆盖率水平是以下几个因素的函数:( a )代码的业务影响/关键性;( b )接触/更改代码的频率;( c )代码生存周期有多长、其复杂性和域变量。我们不能要求每个团队都要达到某个数值(x%)的代码覆盖率;这是一种业务决策,最好由具有特定领域知识的产品负责人来制定。

  • 任何关于“达到 x% 代码覆盖率”的目标都要有相应的基础设施投入,以使测试更容易

比如将工具集成到开发者的工作流程中。值得注意的一点是,工程师们很可能将你定的目标看作是一个最高要求,并以此为目标,采取“达到就行”的工作态度。此时,这个原本定义的“覆盖率下限”就会变成团队的“上限”。所以,你这么做的时候,一定要谨慎。

  • 一般来说,很多产品的行覆盖率没有达到一般水平;我们的目标应该是全面且显著地提高代码覆盖率

虽然没有一个“理想的代码覆盖率数值”,但是在谷歌,我们使用下面的数值作为我们的推荐标准: 60% 是“可接受的 ( acceptable )” , 75% 是“值得肯定的( commendable )”,而 90% 是 “楷模典范( exemplary )”。然而,我们并不愿意自上而下对这一指标做出统一要求,而是更加鼓励每个团队选择对其业务需求有意义的数值。

  • 我们不应该纠结于如何从 90% 的代码覆盖率提高到 95%

代码覆盖率的增加与超过某一点的收益之间是对数关系。但我们应该采取具体措施,从30%提高到70%,并始终确保新代码符合我们期望的阈值。

  • 比覆盖率更重要的是:团队对那些未被覆盖的实际代码行(和行为)的判断(即:分析测试中的差距),以及这种风险是否可接受

关注那些没有被覆盖的代码可能比关注那些已被覆盖的代码更有意义。在 Code Review 过程中,对未覆盖的代码进行的实效性讨论要比对某个覆盖率目标数字更有价值。在 Code Review 过程中,如果能够看到代码覆盖率以及哪些代码没有被覆盖,可以使 Code Review 过程更快且更容易。并非所有代码都有同等重要性,例如,调试用的测试日志通常没有那么重要。因此,当开发人员不但能看到覆盖率数字,而且可以看到作为 Code Review 一部分,突出显示出哪些代码被覆盖了,更容易确保覆盖了最重要的代码。

  • 仅仅因为你现在的产品代码覆盖率低,并不意味着你无法采取具体且渐进的步骤来改进它。

接手一个代码覆盖率低且可测试性都很差的遗留系统可能会让你望而生畏,而且你可能会觉得没有能力扭转局面,甚至不知道从哪里开始。但至少,你可以采用“童子军规则”(让营地比你刚到达时更干净)。随着时间的推移,逐渐地,你就会达到一定的健康度。

  • 确保一定覆盖那些频繁更改的代码

虽然“项目全量覆盖率超过 90% ”的目标很可能是不值得的,但是,“每次提交覆盖率的目标设定为 99% ”是合理的,而 90% 是一个很好的最低阈值。我们需要确保我们的测试不会随着时间的推移而变得越来越糟。

  • 集成/系统测试覆盖率也很重要

单元测试代码覆盖率仅仅是覆盖率难题中的一部分,集成/系统测试覆盖率也很重要。而且,部署流水线( Deployment Pipeline )中所有覆盖率(包括单元测试和集成测试)的聚合视图也是至关重要的,因为它为您提供了一个更大的图景,即当您的代码通过部署流水线到达生产环境时,有多少代码没有被测试自动化执行就一目了然了。值得注意的一点是,单元测试在其所覆盖的代码和要被评估的代码之间有很高的相关性,而集成测试和端到端测试的一些代码行覆盖率是有一定的偶然性,可能并不是你有意为之的。之所以获取集成测试中的代码覆盖率,是为了帮助你避免产生错误的安全感,即:即便在单元测试中覆盖的产品代码,你也会认为您在集成测试中覆盖了代码。

  • 应该为部署( Deployment )设定一个代码覆盖率红线标准。

团队应该讨论并决定哪种红线控制机制对他们有意义。但是,你也应该注意,不要把其做成了一个 checkbox,因为可能会适得其反(即:“达到指标”的压力几乎永远不会产生预期的结果)。

有很多机制可以使用,例如:全量代码覆盖率与新增代码覆盖率的红线;固定的代码覆盖率及前一版本的差异率( gate-on-delta )红线等。

团队应该共同致力于维护这些红线,防止那些没有达到上述红线的代码被提交或部署到生产环境中。


原文作者:Carlos Arguelles, Marko Ivanković‎, and Adam Bender

原文链接:Code Coverage Best Practices

发表时间: AUG 07, 2020