代码覆盖率:如何测量、理解指标并改进你的测试
[LOADING...]
代码覆盖率 是每个开发团队都会讨论的指标之一,但很少能充分发挥其潜力。无论你是交付 SaaS 产品还是维护遗留单体应用,了解你的测试实际覆盖了什么——又遗漏了什么——都可能关系到一次自信的发布还是凌晨两点的告警电话。
本文带你了解核心覆盖率指标、如何用现代工具进行测量,以及如何在不浪费精力于无关测试的前提下,切实提升代码覆盖率。
代码覆盖率报告功能在 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 示例:
如果你的唯一测试是调用 foo(1, 1),那么每一行都会执行——但 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 文档获取针对语言的配置说明。
覆盖率指标
覆盖率通常报告为测试过程中所练习代码元素的百分比:
- 行覆盖率 = 已执行的可执行行数 ÷ 总可执行行数。
- 分支覆盖率 = 已执行的分支数 ÷ 总分文数。
不同的指标提供不同级别的信心。行覆盖率指示哪些代码已被执行,而分支覆盖率则揭示每个决策路径是否都经过了测试。
测量代码覆盖率需要使用工具分析源代码的执行部分,然后将结果展示在开发者可以操作的地方——例如 Pull Request 注释、IDE 插件或 CI 仪表盘。不同工具和语言之间的精确覆盖率计算可能略有差异,但行覆盖率和分支覆盖率是最常用的指标。
[LOADING...]
理解覆盖率百分比与“良好的”代码覆盖率
如果孤立地看待,标题级的覆盖率数字可能具有误导性。一个行覆盖率为 85% 的项目可能只有 55% 的分支覆盖率,这意味着复杂逻辑未被充分测试。覆盖率指标提供的是关于测试执行的洞察,而非测试质量。一个运行了每一行但没有进行任何断言的测试,会带来虚假的安全感。
那么什么才算良好的覆盖率?以下是一些实用指南:
- Google 认为 60% 是可接受的,90% 是优秀的 代码覆盖率。
- 开发者共识建议以 70% 到 80% 为目标,以便在质量与避免过度工程化之间取得平衡。
- 一项研究发现,47 个项目的平均代码覆盖率为 74-76%,表明大多数团队处于类似范围。
- UJET 使用工具进行可见性和趋势跟踪,维持了 85% 的代码覆盖率要求。
对许多人来说,将关键业务逻辑的覆盖率目标定为 80% 是一个健康的行业标准。但 100% 的代码覆盖率并不能保证没有缺陷的代码——它只是意味着每一行都被触及到了,而不是每一个行为都得到了验证。
根据风险级别设置阈值:
- 金融交易模块、认证流程 → 85-90%
- 通用业务逻辑 → 70-80%
- 生成的代码或样板代码 → 完全从阈值中排除
将覆盖率视为随时间变化的趋势指标(周环比、冲刺环比),而非一次性目标。当数字开始下降时,高覆盖率数据能指示遗产代码或未测试代码相关的风险。可视化这一趋势的仪表盘有助于团队在问题累积之前发现回归。
如何在不牺牲质量的前提下提升代码覆盖率
要有效地提升代码覆盖率,应将其视为一套实用的行动指南,而非一个抽象的目标。以下是一种逐步推进的方法,始终将软件质量置于首位。
1. 从覆盖率报告开始。 使用覆盖率报告识别未测试的代码部分,重点关注高风险、低覆盖率区域,如核心领域服务、支付处理和数据访问层。不要试图均匀提升全局覆盖率百分比。
2. 首先编写有针对性的单元测试。 单元测试有助于快速提高代码覆盖率,因为它们针对的是独立的纯函数和小型组件。使用 JUnit 5、pytest 或 Jest 等框架,围绕业务影响最大的函数编写额外的测试。
3. 定位未覆盖的分支和条件。 行覆盖率仍然是衡量代码库被自动化测试执行比例的最常见方式。然而,你可以看得更远。编写测试用例来覆盖错误路径、重试逻辑、超时处理以及复杂逻辑中的边界情况。这将在分支和条件级别提升代码覆盖率,而这正是缺陷最常隐藏的地方。
4. 重构过于复杂的代码。 圈复杂度非常高的函数难以测试和覆盖。将它们拆分为更小、可测试的单元,既能提高覆盖率数字,也能改善静态分析结果。
5. 处理难以测试的代码。 对于第三方集成、遗留模块或具有大量 I/O 的代码,使用依赖注入、模拟、测试替身和契约测试。这些策略使得无需完整的外部环境即可编写测试。
6. 将覆盖率检查集成到 CI 中。 将代码覆盖率工具集成到 CI 流水线中,将目标设置为 80%,然后跟踪进度。利用 GitHub、GitLab 或 Azure DevOps 上的覆盖率状态检查,强制要求“每个 Pull Request 没有显著的覆盖率回归”。
7. 根据项目关键性设定现实的覆盖率目标。 并非每个文件都需要相同的目标。按模块而非全局设定覆盖率目标。
避免为了单纯提高覆盖率百分比而编写肤浅的测试。有用的测试应该断言有意义的行为、边界情况和错误条件——而不仅仅是执行代码行。
代码覆盖率通过验证关键路径和逻辑来提高代码可靠性,但只有当测试本身有意义时才成立。代码覆盖率通过衡量测试的彻底性来加强质量保证,而不是通过填充数字。
将代码覆盖率与静态分析和 CI/CD 工作流集成
将覆盖率工具与静态代码分析平台结合,能为你提供更全面的代码健康视图:运行时执行指标,加上跨多种语言的结构和风格检查。## 报告概述
在准备项目并运行代码覆盖率后,您可以通过 Qodana Cloud 或 IDE 查看代码覆盖率报告。
Qodana Cloud
您可以在 Qodana 报告 界面的右上角找到代码覆盖率统计信息。它还列出了该功能所使用的检查项。
[LOADING...]
IDE
您可以使用 IntelliJ IDEA、WebStorm、PhpStorm、PyCharm 和 GoLand IDE 查看代码覆盖率报告。此功能适用于从 Qodana Cloud 检索(链接后)的报告,或本地存储的报告。
目前,代码覆盖率概览不适用于 .NET 覆盖率报告生成的 XML 格式报告。
从 Qodana Cloud 打开报告
- 在 IDE 中,导航至 工具 | Qodana | 登录 Qodana。[LOADING...]
- 在“设置”对话框中,点击 登录。[LOADING...]
这将重定向到身份验证页面。 - 在“设置”对话框中,搜索要链接的项目。[LOADING...]
在 IDE 中查看覆盖率报告
您可以使用 JetBrains IDE 在本地查看代码覆盖率报告。
在 IDE 中,导航至 运行 | 显示覆盖率数据,然后打开包含代码覆盖率报告的文件。
[LOADING...]
在“覆盖率”工具窗口中,您可以查看测试覆盖率报告。该报告显示已执行或已被测试覆盖的代码的百分比。
[LOADING...]
报告概览
IDE 使用颜色标记来突出显示代码库的测试覆盖率。默认情况下,绿色表示特定行已被覆盖,红色表示代码行未被覆盖。
如果您发现代码覆盖率结果看起来不完整,则可能需要重新配置代码覆盖率工具并生成新的代码覆盖率报告。
[LOADING...]
报告显示实现方法、函数或类逻辑的行的覆盖率,但不包括函数、方法或类声明。下图显示第 7 行不适用覆盖率,而第 8 行未被覆盖。
[LOADING...]
使用 Qodana,您可以在洞察仪表板中查看代码覆盖率。
[LOADING...]
在此处了解更多关于代码覆盖率和其他功能的信息。
关于高代码覆盖率的常见陷阱和误解
高代码覆盖率可能带来虚假的安全感。考虑一个测试套件,其行覆盖率为 100%,但如果断言薄弱或完全缺失,严重缺陷仍可能未被察觉地溜走。执行本身不是验证。
需要注意的陷阱:
- 完全忽略其他类型的覆盖率。 Qodana 支持整体行覆盖率和新增行覆盖率,使团队能够专注于确保新添加或修改的代码得到适当测试,同时逐步改进其余代码库的覆盖率。然而,我们鼓励您随时间推移探索其他类型,例如分支覆盖率。
- 将覆盖率用作绩效指标。 当单一的全局覆盖率百分比成为团队 KPI 时,会激励维持浅薄的测试——刚好达到数字即可。这可能会减慢开发过程,而不会带来有意义的收益。
- 强制覆盖不可测试的代码。 生成的代码、简单的 getter/setter 以及框架粘合代码确实没有必要测试。努力在这里强制覆盖是浪费精力。将它们排除在阈值之外。
- 忘记覆盖率不衡量什么。 覆盖率工具不会评估所有业务需求、用户旅程或非功能方面(性能、安全性、可用性)是否得到充分测试。它们仅报告哪些代码被执行了。
微软研究院对 100 多个大型开源 Java 项目进行了研究,发现在文件级别上,高覆盖率与发布后缺陷减少之间的相关性并不显著。这再次证实覆盖率是必要的但不充分——需要与缺陷趋势、事故复盘、静态分析警告和生产监控相平衡。
常见问题解答
在实际项目中是否曾要求 100% 的代码覆盖率?
是的,在安全关键领域。航空航天软件根据 DO-178C 和汽车系统根据 ISO 26262 可能需要接近 100% 的语句、分支和 MC/DC 覆盖率,具体取决于与安全完整性级别相关的一个或多个标准。对于大多数商业 Web 和移动应用程序,80% 的代码覆盖率通常被认为是核心模块的良好目标,超过此目标则收益递减。测试质量和基于风险的关注比在整个代码库中追求 100% 更重要。
我应该多久在项目中运行一次覆盖率分析?
在活跃仓库中,每次拉取请求都运行覆盖率,以便立即捕获回归。对于集成和端到端测试较慢的大型软件项目,安排一次夜间或每周的完整覆盖率作业。使用趋势数据指导重构投资,并确定最需要额外测试的位置。
我应优先考虑哪种覆盖率指标:行覆盖率、分支覆盖率还是其他?
从行覆盖率开始,作为简单的基线,易于与利益相关者沟通。一旦团队熟悉,引入分支覆盖率作为非平凡业务逻辑的主要指标。条件覆盖率和路径覆盖率在特别复杂、风险较高的代码中最有用——例如授权检查、定价引擎或任何具有深度嵌套控制结构的程序——并且可以有针对性地选择使用,而非全项目应用。
静态代码分析和代码覆盖率如何相互补充?
静态分析在不运行代码的情况下标记潜在问题,包括死代码、空指针解引用、安全缺陷。代码覆盖率显示代码的哪些部分实际被测试执行。它们共同形成一个反馈循环:静态分析识别复杂或有风险的区域,覆盖率报告揭示这些区域是否得到充分覆盖,开发人员据此优先安排新测试或重构。
我可以将代码覆盖率用于手动测试还是仅用于自动化测试?
覆盖率工具默认与自动化测试配合使用,但它们也可以在针对已注入检测的应用程序运行手动探索性测试时收集数据。这在预发布强化阶段非常有用,用于确认哪些功能已被执行。然后可以将最有价值的手动场景转换为自动化测试,以便在软件项目中随时间推移保留该覆盖率。
注意: 虽然本文中的示例使用了特定的覆盖率工具,但 Qodana 并不绑定于任何特定的解决方案。任何能够生成受支持的覆盖率报告格式的工具都可以使用。
特别感谢 Andrei Iurko 和 Ivan Efiminov 对本篇文章的帮助。