"覆盖率 85%“是测试报告里最常被引用、也最容易被滥用的数字。行数覆盖率 100% 的系统照样天天出事故——覆盖的是"代码被执行",不是"行为被验证"。这篇讲覆盖率的三种形态、各自的正确用法,以及怎么从"行数崇拜"走到"风险覆盖”。
一、先说清楚:行数覆盖率度量的是什么
1 | # coverage.py 的标准产出 |
它只回答一个问题:哪些代码被测试执行到了。它不回答:
- 执行到的分支断言了什么?(执行了
if x: refund()但没断言 refund 的行为 = 伪覆盖) - 没执行的代码重要吗?(未覆盖的 12 条可能是死代码,也可能是资金逻辑)
- 关键路径测透了吗?(一个分支覆盖 95%,但核心分支的断言只有
assert status != None)
行数覆盖率是必要非充分条件——它的正确角色是"发现盲区",不是"质量证明"。
二、三种覆盖率,各管一件事
1. 代码覆盖率(行数/分支):找盲区
1 | pytest --cov=src --cov-branch --cov-report=term-missing |
正确用法:
- 看 miss 列表,不看百分比:85% 本身无意义,
order.py:142-158 未覆盖才有意义——逐条判断"这段为什么没覆盖,该不该覆盖"; - 分支覆盖比行覆盖诚实:
if只测了真分支,行覆盖 100%,分支覆盖 50%——资金/状态机代码强制开--cov-branch; - 基线 + 增量:存量代码覆盖率是历史债(可能 40%),门禁卡"增量覆盖率"(新增/改动代码 ≥ 80%),存量只要求"不下降"——卡全量 80% 的结果是没人动老代码,也没人补老代码的用例。
2. 需求覆盖率:测全了没有
1 | 需求覆盖率 = 被至少一条用例覆盖的需求点 / 需求点总数 |
需求覆盖率是"完整性"的度量(对应用例设计那篇的完整性层),它和代码覆盖率正交:代码覆盖率 90% + 需求覆盖率 60% = “测得很细但漏了整个功能分支”。
3. 风险覆盖率:测得值不值
1 | 风险覆盖率 = 被用例覆盖的风险项 / 风险清单 |
风险覆盖率是三者里最"软"但最贴近决策的:发版评审问的不是"覆盖率多少",是"本次改动的风险面,哪些测了、哪些没测、没测的理由是什么"。
三、让覆盖率"可用"的工程动作
1. 覆盖率进 CI,但门禁分级
1 | # CI 覆盖率门禁(pytest-cov 的 fail_under 分模块配置) |
门禁的目的是"引导用例投在哪",不是"卡人"——资金模块 90%、管理后台 50%,差异本身就是测试策略的声明。
2. 覆盖率趋势图 + 盲区清单(不是数字看板)
1 | 覆盖率看板只放两样: |
"数字 + 排序"才驱动行动,裸百分比只是给领导看的。
3. 伪覆盖检测:执行了但没断言 = 0 分
1 | # 反模式:这个用例贡献了代码覆盖,没贡献行为验证 |
伪覆盖率比不覆盖更危险——它制造"很安全"的错觉。CI 里加断言 lint,把"执行覆盖"和"断言覆盖"分开统计。
四、和自动化体系打通:覆盖率数据的三个出口
1 | ┌→ 门禁:增量覆盖率 < 80% → MR 不合并 |
覆盖率给研发看的版本和给测试看的不同:研发关心"我的改动被测没测",所以按"本次 diff × 覆盖"展示——覆盖率数据第一次对"改代码的人"有意义,而不是只考核"写用例的人"。
五、覆盖率永远不该回答的问题
| 问题 | 为什么覆盖率答不了 |
|---|---|
| “系统质量好不好” | 覆盖率只度量"测了多少",不度量"测得对不对"(断言质量) |
| “能不能发版” | 发版决策看风险敞口(风险覆盖率 + 缺陷状态),不看行数 |
| “哪个团队测试做得好” | 用绝对值横向比较 = 鼓励刷分(写无用例凑覆盖是真实发生的) |
刷分案例(真实见过):为凑覆盖写了 200 个"只调用不断言"的用例,覆盖率 85% → 96%,三个月后同类缺陷照出——度量会塑造行为,卡错指标就收获刷分。
六、小结
覆盖率的正确姿势:行数覆盖找盲区(看 miss 不看百分比)、分支覆盖验逻辑(资金模块强制)、需求覆盖验完整(评审拆解需求点)、风险覆盖验值得(发版决策依据)、断言质量防伪覆盖(执行 ≠ 验证)。覆盖率是镜子不是奖状——它照出"没测到哪",但"测得值不值"永远是风险模型和断言质量的事。