代码覆盖率陷阱:为何优化代码反而降低覆盖率

本文深入探讨代码覆盖率指标的局限性,通过实际案例展示DRY原则如何意外降低覆盖率,分析80%阈值的随意性,并提供通过代码结构调整提升覆盖率准确性的实验证明。

代码覆盖率陷阱:为何优化代码反而降低覆盖率

统计学谎言与代码覆盖率

“世界上有三种谎言:谎言、该死的谎言和统计数据” - 据称出自马克·吐温

某些广泛使用的数字和计算,在没有额外信息的情况下,会给人错误的印象。以身体质量指数(BMI)为例,它被用作衡量一个人是否处于健康体重的方法。BMI使用个人的身高和体重计算。通常BMI达到25或以上被视为超重。

代码覆盖率的崛起与问题

代码覆盖率似乎突然在软件开发中获得了大量青睐。我有多次经历,保持至少80%代码覆盖率的重要性影响了我所做的决策、我编写的代码,但并不总是朝着更好的方向。

为什么测试覆盖率成为了代码质量的代理指标?我阅读了相关问题和历史,进行了一个实验,并得出了一些普遍观察:

  • 并非所有文件、功能或应用程序都具有同等价值,但代码覆盖率工具会同等对待它们(未经自定义时)
  • 自动化测试并非总是测试应用程序最具成本效益的方式
  • 默认的80%最低代码覆盖率是武断的
  • 使代码更DRY(不要重复自己)会使代码覆盖率更差
  • 代码结构方式可以提高代码覆盖率指标的准确性

80%阈值从何而来?

我进行了必要的谷歌搜索,希望能找到80%默认阈值背后的一些科学证明的推理。可惜我一无所获。

不过我找到了一些可信的推测,认为80%与帕累托原则相关。这很有道理,除非你真正理解帕累托原则中80%的含义。

帕累托原则规定"大约80%的后果来自20%的原因"。将帕累托原则应用于测试的一个例子如下:“80%的投诉来自20%的重复出现的问题。”

DRY原则与代码覆盖率的冲突

随着代码库的发展,会出现改进的机会。最常见的实践之一是合并重复的代码。你的代码库可能有一个或多个代码块被复制粘贴到其他地方。

在多个位置拥有相同的代码通常被视为不良实践,将重复使用的代码块移动到单个位置是有意义的。这个共享代码可能仍在同一个文件中,或者移动到单独的文件中。这是DRY(不要重复自己)原则,与WET(每次都要写)代码相对。

使代码更DRY通常被认为是好事。然而,这是以代码覆盖率下降为代价的。以下是一些假设数字:

WET代码:100行,80行被覆盖 = 80% DRY代码:90行,70行被覆盖 = 77.8%

代码结构如何改变覆盖率指标的准确性

关于代码覆盖率有个好消息。你可以根据代码结构方式提高覆盖率数据的质量。但有一个问题——它需要尽可能以冗长和明确的方式编写代码。

我进行了一个实验来测试将if-else块减少为三元运算符是否影响代码覆盖率。剧透警告:我不确定这种减少总是正确的选择。

实验使用了JavaScript编写函数,使用Jest测试框架(默认使用Istanbul)。代码库包含所述函数的多个版本,每个版本都有自己的测试文件。

当比较代码覆盖率结果时,具有最简洁代码的condition_control.js文件显示100%代码覆盖率,尽管四个测试中有三个已被禁用。其他版本更准确地显示了代码覆盖率缺失的位置。

使用switch语句且每个条件在单独行上的代码版本显示25%分支覆盖率和50%行覆盖率。这似乎表明,代码越简洁,就越容易通过次标准的测试覆盖率获得夸大的代码覆盖率分数。更冗长的代码更易于阅读和理解,并提供更准确的代码覆盖率数据。

结论:测试如保险

测试和代码覆盖率与保险有很多共同点。两者都提供一定程度的安全性并有成本。你为房屋或汽车保险支付的保费必须值得你保险的物品。测试和代码覆盖率工具也有成本:开发人员编写测试所需的时间和精力,测试套件在流水线中的执行等。不仅仅是用于监控代码覆盖率的软件成本。所有产生的成本必须值得你保护的东西的价值。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计