覆盖率当 KPI 的副作用
覆盖率衡量的是代码被执行过的比例,不衡量断言是否有效。一旦把它设为强制目标,团队会自然走向最容易达标的路径:调用一遍被测代码而不写断言、为覆盖而写无意义的用例、绕过难以测试的逻辑。可用的替代口径是变更集覆盖率,即只看本次改动新增与修改的行,同时把存量覆盖率作为长期趋势跟踪。
口径对比
| 常见反效果 | 数量达标而质量缺失,断言形同虚设 |
|---|---|
| 替代口径 | 变更集覆盖率,只看本次改动的行 |
| 配套手段 | 关键模块用变异测试抽检断言强度 |
| 存量处理 | 作为长期趋势跟踪,避免历史包袱压瘫门禁 |
典型使用场景
- 新代码门禁:要求变更集覆盖率达到阈值,比拉高整体覆盖率更可行
- 遗留系统改造:存量不做硬性要求,只对新增与修改部分设门槛
- 测试评审:检查测试是否包含断言,而不只统计执行行数
- 关键模块把关:对计价、结算等高价值模块补变异测试抽检
- 覆盖率下降归因:定位是哪次改动引入未覆盖分支,而不是全员追数
反面示例与改进
反面示例与改进
# 反面:执行了但没有断言,覆盖率涨了,质量没变
void testSomething() {
service.process(input); # 无任何断言
}
# 改进:先声明期望,再执行并断言结果与边界
void testRejectWhenAmountNegative() {
assertThrows(() -> service.process(negative)); # 断言异常类型
}指标用法对照
| 用法 | 效果 | 风险 |
|---|---|---|
| 整体覆盖率当门禁 | 容易通过豁免与生成代码绕过 | 存量系统长期红灯,团队逐渐忽略 |
| 变更集覆盖率当门禁 | 与本次改动直接相关,可执行 | 需要正确计算对比基线 |
| 变异测试抽检 | 反映断言强度 | 成本高,只适合关键模块抽检 |
| 覆盖率趋势跟踪 | 观察长期方向 | 只读趋势不做门禁,容易无人关注 |
变更集覆盖率要正确计算对比基线:基线取错会把无关的历史修改计入本次变更,导致数值失真。确认基线分支与合并策略之后再设置门槛。
覆盖率是发现缺口的地图,不是质量结论。看到未覆盖区域时,先判断这是否是值得测的分支,再决定补测试还是显式排除并说明理由。
落地检查清单
- 门禁只约束变更集,存量作为趋势跟踪
- 生成代码与第三方代码已排除
- 测试评审检查断言内容,而不只看覆盖数字
- 门槛阈值有基线依据,并定期重新校准