Code Coverage: How to Measure It, Understand the Metrics, and Improve Your Tests

TL;DR · AI 摘要
代码覆盖率是衡量测试质量的关键指标,但需结合静态分析并避免盲目追求100%。
核心要点
- 80%是关键业务逻辑的合理覆盖率目标,100%通常不现实
- JaCoCo/Istanbul/Coverage.py等工具可生成CI流水线覆盖率报告
- 覆盖率应与静态分析结合,分别解决执行路径验证和结构风险检测
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- 代码覆盖率
- 核心指标
- 行/分支/条件覆盖率
- 与静态分析互补
- 实践方法
- 工具链(JaCoCo/Istanbul)
- CI/CD集成
- 80%合理目标
- 常见误区
- 高覆盖率≠无缺陷
- 避免盲目追求100%
金句 / Highlights
值得收藏与分享的关键句。
80%是关键业务逻辑的合理覆盖率目标,100%通常不现实且成本高昂
覆盖率报告应指导测试优化,优先提升高风险模块的测试覆盖
静态分析发现结构风险,覆盖率验证测试是否触及这些风险区域
代码覆盖率 | JetBrains Qodana
Qodana
面向团队的代码质量平台
关注
- 关注:
- X X
- RSS RSS
获取 Qodana
代码覆盖率:如何衡量它、理解指标并改进测试
Kerry Beetge
代码覆盖率是每个开发团队都会讨论但很少充分发挥其潜力的指标之一。无论您是在发布 SaaS 产品还是维护遗留的单体架构,理解测试实际覆盖的内容以及遗漏的部分,可能在自信发布和凌晨 2 点的告警页面之间产生决定性差异。
本文将带您了解核心覆盖率指标,如何使用现代工具进行衡量,以及改进代码覆盖率的实际步骤,而无需在无关紧要的测试上浪费精力。
代码覆盖率报告在 Qodana Ultimate 和 Qodana Ultimate Plus 许可证中提供。要了解可用的 Qodana 许可证,请访问订阅选项和定价页面。您也可以请求演示。
TL;DR
- 代码覆盖率是衡量自动化测试执行源代码百分比的软件测试指标。它直接影响软件质量和部署信心。
- 衡量代码覆盖率始于选择合适的指标(行、分支、条件和路径覆盖率),并使用 Qodana、JaCoCo、Istanbul/NYC、Coverage.py 或 Jest 等工具在 CI 流水线中生成覆盖率报告。
- 通常认为关键业务逻辑的 80% 代码覆盖率是一个良好的目标,但实现 100% 代码覆盖率通常不切实际且成本高昂。高覆盖率从不保证软件无缺陷。
- 要提高代码覆盖率,请使用覆盖率报告识别未测试的代码部分,优先处理高风险模块,并将覆盖率检查集成到 CI/CD 工作流程中。
- 要深入了解覆盖率如何与结构检查和风格检查相结合,请参考我们的专用静态代码分析指南。
什么是代码覆盖率?
代码覆盖率衡量测试期间执行的代码百分比。如果代码库有 1000 行可执行代码,测试套件运行了其中的 900 行,则行覆盖率是 90%。通常以百分比表示,作为运行时指标——它跟踪实际执行的内容,而不是仅基于结构可能存在问题的内容。
这使其区别于静态代码分析,后者在不执行代码的情况下检查源代码,以标记复杂性、风格问题或安全异味。两者相辅相成:静态分析告诉您潜在问题可能出在哪里,而代码覆盖率告诉您测试是否实际到达了这些区域。我们在此处详细阐述这一点。
有必要澄清代码覆盖率与有时称为测试覆盖率之间的区别:
- 代码覆盖率关注测试套件运行期间执行了多少源代码——行、分支、条件。
- 测试覆盖率范围更广。它询问业务需求和用户行为是否得到验证,即使每行相关代码都被执行。
例如,测试套件可能执行支付模块中 95% 的代码行,从而实现高行覆盖率。但如果没有任何测试检查在负余额时支付是否能优雅失败,则针对该行为的测试覆盖率指标是不完整的。
代码覆盖率可识别未测试的代码,帮助开发人员改进测试工作,特别是在认证、数据访问层和错误处理等关键部分。测量代码覆盖率有助于发现未测试的路径和边缘情况,这些可能隐藏回归问题或安全漏洞。
以下是一个 JavaScript 的快速示例:
function foo(a, b) {
let c = 42
if (a > 0 && b > 0)
c = c + a * b
return c
}
如果唯一测试调用 foo(1, 1),所有代码行都会执行,但 else 分支从未被测试。行覆盖率看起来完整;分支覆盖率则揭示了这一差距。
核心代码覆盖率指标和标准
覆盖率标准是用于确定测试期间代码执行彻底性的正式规则。与其同时追逐所有指标,团队通过选择少量主要覆盖率指标并集中精力处理,往往能获得更好的结果。以下是您可能遇到的常见覆盖率类型。
| 指标 | 定义 | 最适合用于 | |--------------|----------------------------------------------------------------------|------------------------------| | 行/语句 | 检查每行代码是否已执行。语句覆盖率衡量程序中已执行的语句。 | 基线仪表板 | | 函数 | 函数覆盖率检查每个函数是否至少被调用一次。 | 识别死代码 | | 分支 | 分支覆盖率确定控制结构的分支执行数量(例如,if/else 的两侧)。 | 决策逻辑 | | 条件 | 条件覆盖率测试布尔子表达式在真和假情况下被评估的数量。 | 复杂谓词 | | 路径 | 路径覆盖率测试代码中所有可能的执行路径。 | 关键算法 |
行覆盖率和语句覆盖率通常由大多数工具报告为单一百分比,使它们成为最简单的基线指标。但高行覆盖率并不意味着其他指标得分高。例如,如果有 5 个函数,其中 4 个各包含 1 行代码,最后一个包含 100 行代码,而测试仅覆盖了最后一个函数,代码覆盖率将接近 100%,但函数覆盖率仅为 20%。
条件覆盖率更进一步。对于复合表达式(如 if (a && b || c)),条件覆盖率要求每个布尔子表达式(a、b、c)在测试用例中同时评估为真和假。这可以揭示分支覆盖率可能隐藏的缺失测试。
对于非平凡的应用程序,由于组合爆炸(循环和嵌套分支使代码路径呈指数级增长),完全路径覆盖率在实践中几乎不可能实现。它仅对特别关键的算法仍有用。
在安全关键领域,需要更高级的标准,如修改条件/决策覆盖率(MC/DC)。例如,DO-178C 要求 Level A 航电软件使用修改条件/决策覆盖率(MC/DC),证明每个条件独立影响每个决策的结果。
如何在实践中测量代码覆盖率
以下是测量代码覆盖率的四个步骤:
- 插入代码探针 – 通过引入钩子使覆盖率工具能够观察执行情况。这可以通过源码插桩、字节码插桩或运行时插桩实现。
- 运行自动化测试 – 单元测试、集成测试或端到端覆盖率测试。
- 收集覆盖率数据 – 记录执行了哪些行、分支和条件。
- 生成覆盖率报告 – HTML 仪表板、终端摘要或机器可读格式(Cobertura XML、LCOV)。
在实践中运行覆盖率
Qodana 支持由多种流行工具生成的覆盖率报告,包括:
- JaCoCo(Java/Kotlin)
- Jest 和 Istanbul/nyc(JavaScript/TypeScript)
- pytest-cov(Python)
- Coverlet(.NET)
- PHPUnit(PHP)
- go test(Go)
有关特定语言的配置说明,请参阅 Qodana 文档。
覆盖率指标
覆盖率通常以测试过程中执行的代码元素百分比形式报告:
- 行覆盖率 = 执行的可执行行数 ÷ 总可执行行数。
- 分支覆盖率 = 执行的分支数 ÷ 总分支数。
不同指标提供的置信度水平不同。行覆盖率表明哪些代码被执行过,而分支覆盖率则揭示是否每条决策路径都经过测试。
代码覆盖率测量涉及使用分析源代码执行部分的工具,然后将这些结果呈现在开发者可以采取行动的地方(如拉取请求评论、IDE 插件或 CI 仪表板)。不同工具和语言的覆盖率计算可能略有差异,但行覆盖率和分支覆盖率是最常用的指标。
理解覆盖率百分比和“良好”的代码覆盖率
如果单独查看覆盖率数字,可能会产生误导。一个项目可能达到 85% 的行覆盖率,但分支覆盖率可能只有 55%,导致复杂逻辑测试不足。覆盖率指标反映的是测试执行情况,而非测试质量。一个测试虽然执行了所有代码行,但没有进行任何断言,会带来虚假的安全感。
那么什么是良好的覆盖率?以下是实用指南:
- Google 认为在其项目中,60% 的覆盖率可接受,90% 的覆盖率堪称典范。
- 开发者共识建议将覆盖率目标设为 70% 至 80%,以平衡质量并避免过度设计。
- 一项研究发现,47 个项目平均代码覆盖率为 74-76%,表明大多数团队处于相似范围。
- UJET 使用可视化和趋势跟踪工具,保持 85% 的代码覆盖率要求。
对许多人来说,关键业务逻辑的 80% 覆盖率是健康的行业标准。但 100% 的代码覆盖率并不能保证代码无缺陷——它仅表示每一行代码都被执行过,并不意味着所有行为都经过验证。
根据风险等级设置阈值:
- 金融交易模块、认证流程 → 85–90%
- 一般业务逻辑 → 70–80%
- 自动生成或样板代码 → 完全排除阈值范围
将覆盖率视为随时间变化的趋势指标(如周对周、冲刺对冲刺),而非一次性目标。当覆盖率数据开始下降时,高覆盖率可能表明遗留代码或未测试代码存在风险。可视化这些趋势的仪表板可以帮助团队在问题累积前发现回归。
如何在不牺牲质量的前提下提高代码覆盖率
要实质性提高代码覆盖率,应将其视为实用操作手册,而非抽象目标。以下分步方法将软件质量置于核心位置。
- 从覆盖率报告入手。使用覆盖率报告识别未测试的代码部分,重点关注高风险且覆盖率低的区域,如核心领域服务、支付处理和数据访问层。不要试图均匀提升全局覆盖率百分比。
- 首先编写针对性的单元测试。单元测试能快速提高代码覆盖率,因为它们针对孤立的纯函数和小型组件。使用 JUnit 5、pytest 或 Jest 等框架,围绕对业务影响最大的函数编写额外测试。
- 针对未覆盖的分支和条件进行测试。行覆盖率仍然是衡量自动化测试覆盖代码库范围的最常用方式。但你可以更进一步。编写测试用例来验证错误路径、重试逻辑、超时处理以及复杂逻辑中的边界情况。这将提高分支和条件层面的代码覆盖率,而这些地方通常是隐藏缺陷的高发区域。
- 重构过于复杂的代码。循环复杂度极高的函数难以测试且难以覆盖。将其拆分为更小的、可测试的单元,既能提升覆盖率数据,也能改善静态分析结果。
- 处理难以测试的代码。对于第三方集成、遗留模块或涉及大量I/O操作的代码,可使用依赖注入、模拟、测试桩和契约测试等策略。这些方法使编写测试成为可能,而无需依赖完整的外部环境。
- 将覆盖率检查集成到CI流程中。将代码覆盖率工具集成到CI流水线中,并设定80%的目标,然后跟踪进度。通过GitHub、GitLab或Azure DevOps上的覆盖率状态检查,强制要求“每个拉取请求不得出现显著的覆盖率退化”。
- 根据项目关键性设定合理的覆盖率目标。并非每个文件都值得追求相同的覆盖率目标。应按模块而非全局设定覆盖率目标。
避免仅为了提升覆盖率百分比而编写表面化的测试。有用的测试应验证有意义的行为、边界情况和错误条件,而不仅仅是执行代码行。
代码覆盖率通过验证关键路径和逻辑来提高代码可靠性,但前提是测试本身具有实际意义。代码覆盖率通过衡量测试的彻底性来加强质量保障,而不是通过虚报数字。
将代码覆盖率与静态分析和CI/CD流程集成
将覆盖率工具与静态代码分析平台结合,能更全面地了解代码健康状况:既包含运行时执行指标,又涵盖多语言和编程语言的结构与风格检查。
报告概览
在准备好项目并运行代码覆盖率后,你可以在Qodana Cloud中查看代码覆盖率报告,或使用IDE进行查看。
Qodana Cloud
在Qodana报告界面右上角可以找到代码覆盖率统计数据。该界面还会列出该功能使用的检查项。
IDE
你可以使用IntelliJ IDEA、WebStorm、PhpStorm、PyCharm和GoLand等IDE查看代码覆盖率报告。此功能适用于从Qodana Cloud链接后获取的报告,或本地存储的报告。
当前,针对.NET覆盖率生成的XML格式报告,暂不支持代码覆盖率概览功能。
#### 从Qodana Cloud打开报告
- 在IDE中,导航至工具 | Qodana | 登录Qodana。
- 在设置对话框中,点击登录。这将跳转到认证页面。
- 在设置对话框中,搜索要链接的项目。
在IDE中查看覆盖率报告
你可以使用JetBrains IDE在本地查看代码覆盖率报告。
在IDE中,导航至运行 | 显示覆盖率数据,并打开包含代码覆盖率报告的文件。
在覆盖率工具窗口中,你可以查看测试覆盖率报告。该报告会显示代码中被执行或被测试覆盖的百分比。
报告概览
IDE 通过颜色标记突出显示代码库的测试覆盖率。默认情况下,绿色表示某一行代码已被覆盖,红色表示未被覆盖的代码行。
如果发现代码覆盖率结果看起来不完整,可能需要重新配置代码覆盖率工具并生成新的覆盖率报告。
该报告显示实现方法、函数或类逻辑的代码行覆盖率,但不包括函数、方法或类的声明行。下图显示第 7 行的代码覆盖率不适用,而第 8 行未被覆盖。
使用 Qodana,您可以在 Insights Dashboard 中查看代码覆盖率。
了解更多关于代码覆盖率和其他功能的信息请访问此处。
关于高代码覆盖率的常见误区
高代码覆盖率可能会带来虚假的安全感。考虑一个测试套件在行覆盖率上达到 100%:如果断言薄弱或完全缺失,严重漏洞可能未被发现。执行本身并不等于验证。
需要警惕的误区包括:
- 完全忽略其他类型的覆盖率。Qodana 支持整体行覆盖率和新增行覆盖率,使团队能够专注于确保新添加或修改的代码得到充分测试,同时逐步提升整个代码库的覆盖率。但我们鼓励您随时间推移探索其他类型,例如分支覆盖率。
- 将覆盖率用作性能指标。当单一的全局覆盖率百分比成为团队 KPI 时,会激励团队仅维护足够达到数字的表面测试。这可能会在没有实际效益的情况下减缓开发进程。
- 在无法测试的代码上强制要求覆盖率。生成的代码、简单的 getter/setter 以及框架胶合代码确实无需测试。在此处强行要求覆盖率会浪费精力。应将这些代码排除在阈值之外。
- 忘记覆盖率无法衡量的内容。覆盖率工具不会评估所有业务需求、用户旅程或非功能性方面(性能、安全性、可用性)是否得到充分测试。它们仅报告哪些代码被执行过。
微软研究院对 100 多个大型开源 Java 项目进行了研究,发现文件级别上高覆盖率与发布后缺陷数量之间几乎没有相关性。这进一步证明覆盖率是必要的但不充分的——需将其与缺陷趋势、事件事后分析、静态分析警告和生产监控相结合。
开始免费 Qodana 试用
常见问题
实际项目中是否需要 100% 的代码覆盖率?
是的,在安全关键领域需要。根据与安全完整性等级相关的标准,遵循 DO-178C 的航空航天软件和遵循 ISO 26262 的汽车系统可能需要接近 100% 的语句、分支和 MC/DC 覆盖率。对于大多数商业 Web 和移动应用,核心模块的代码覆盖率通常以 80% 为合理目标,超过该目标后的收益递减。测试质量和基于风险的优先级比追求整个代码库的 100% 覆盖率更重要。
我应该多久运行一次覆盖率分析?
在活跃的代码库中,应对每个 Pull Request 运行覆盖率分析,以便立即发现回归问题。对于集成和端到端测试较慢的大型软件项目,可安排夜间或每周的完整覆盖率任务。利用趋势数据指导重构投资,并确定最需要补充测试的区域。
我应该优先关注哪种覆盖率指标:行覆盖率、分支覆盖率还是其他?
从易于向利益相关者传达的简单基准线——行覆盖率开始。当团队熟悉后,再引入分支覆盖率作为处理非简单业务逻辑的主要指标。条件覆盖率和路径覆盖率在特别复杂的高风险代码中最有用,例如授权检查、定价引擎或任何具有深度嵌套控制结构的程序——这些可以按需选择性采用,而非全项目范围使用。
静态代码分析和代码覆盖率如何相辅相成?
静态分析无需运行代码即可发现潜在问题,包括死代码、空指针解引用和安全异味。代码覆盖率则显示测试实际执行了源代码的哪些部分。两者共同形成反馈循环:静态分析识别复杂或高风险区域,覆盖率报告揭示这些区域是否得到充分覆盖,开发人员据此优先编写新测试或重构代码。
我能否将代码覆盖率用于手动测试,还是仅限于自动化测试?
覆盖率工具默认与自动化测试配合使用,但也可以在手动探索性测试运行时收集数据(前提是应用程序已进行插桩)。这在发布前强化阶段很有用,可以确认哪些功能已被验证。随后可将最有价值的手动测试场景转换为自动化测试,以在软件项目中长期保持覆盖率。
注意:本文示例使用了特定的覆盖率工具,但 Qodana 并不绑定任何特定解决方案。任何能生成受支持覆盖率报告格式的工具均可使用。
特别感谢 Andrei Iurko 和 Ivan Efiminov 对本文的协助。
- 分享
上一篇
左移静态代码分析 /think