代码覆盖率陷阱:为何优化代码反而降低覆盖率
统计学谎言与代码覆盖率
“世界上有三种谎言:谎言、该死的谎言和统计数据” - 据称出自马克·吐温
某些广泛使用的数字和计算,在没有额外信息的情况下,会给人错误的印象。以身体质量指数(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%行覆盖率。这似乎表明,代码越简洁,就越容易通过次标准的测试覆盖率获得夸大的代码覆盖率分数。更冗长的代码更易于阅读和理解,并提供更准确的代码覆盖率数据。
结论:测试如保险
测试和代码覆盖率与保险有很多共同点。两者都提供一定程度的安全性并有成本。你为房屋或汽车保险支付的保费必须值得你保险的物品。测试和代码覆盖率工具也有成本:开发人员编写测试所需的时间和精力,测试套件在流水线中的执行等。不仅仅是用于监控代码覆盖率的软件成本。所有产生的成本必须值得你保护的东西的价值。