覆盖率策略
把覆盖率当未测风险地图,非最大化的百分比。
为什么
覆盖率是测试中最被误解的指标。100% 行覆盖不意味 100% 测试过——它意味每行至少执行一次,非每行被断言。零断言 100% 覆盖套件全绿守卫啥也不。覆盖率价值不在数字;在它产的图:业务逻辑中未覆盖分支是 bug 可藏不被抓处。当风险地图用,覆盖率指导下一条测试写哪。当目标用,它激励平凡测试涨计数而不验行为,数字变谎言。
何时使用
每次 CI 运行生成覆盖率报告,写新代码或规划测试冲刺时审查。不要用覆盖率阈值门控合并——门控新代码未覆盖分支是否在业务逻辑(须覆盖)还是平凡代码(可接受)。
如何做
1. 在测试运行器配覆盖率收集
- Vitest: coverage: { provider: "v8", reporter: ["text", "html", "lcov"] }
- 仅含 src/(业务逻辑),排除 tests/、配置、入口点
- 用分支覆盖非仅行覆盖——分支抓 if/else 缺口
2. 生成报告读 HTML 视图
- 开 coverage/index.html——红行未覆盖,黄行部分分支
- 聚焦业务逻辑文件(src/lib、src/services),非配置或 UI 壳
- 记具体未覆盖分支,非仅百分比
3. 分类每个未覆盖分支
- "须覆盖"——业务逻辑、错误处理、边界条件 → 写测试
- "应覆盖"——较不关键但仍逻辑 → 有时间写
- "可接受"——平凡 getter、框架样板、仅错误日志 → 跳过
4. 为"须覆盖"分支写测试,非为涨百分比
- 目标是消除风险非涨计数
- 覆盖分支但不断言的测试无价值——验证行为
5. 追踪覆盖率趋势,非绝对数
- 变更文件覆盖下降是有人跳测试的信号
- 平凡测试填充的上涨非改善——看变异分数示例
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
coverage: {
provider: "v8",
reporter: ["text", "html", "lcov"],
include: ["src/lib/**", "src/services/**"], // 仅业务逻辑
exclude: ["**/*.config.*", "**/index.ts", "**/*.test.ts"],
branches: 100, // 报告所有未覆盖分支
lines: 80, // 业务逻辑行覆盖 < 80% 警告
},
},
});
// 测试运行后开 coverage/index.html:
// src/lib/discount.ts — 100% 行,75% 分支(SAVE20 的 else 未测)
// src/lib/pricing.ts — 60% 行(批量折扣分支从未执行)
// src/services/checkout.ts — 90% 行,50% 分支(错误路径未测)
//
// 行动: 为 checkout.ts 错误路径和 pricing.ts 批量折扣写测试。
// 别为 discount.ts 凑 100%——未覆盖分支是平凡 else-return,
// 测试只增噪声不抓 bug。常见问题
- ⚠对每个文件设硬 80% 覆盖门控激励开发者为样板代码(getter、框架钩子)写平凡测试只为过门控,增噪声不减风险。
- ⚠仅报行覆盖藏最重要缺口——每行都跑但 if 和 else 分支从未都执行的函数有 100% 行覆盖 50% 分支覆盖。
- ⚠追全仓 100% 覆盖(含配置、入口点、类型定义)浪费在无法有意义测试的代码上,稀释对业务逻辑的聚焦。
- ⚠把覆盖率当唯一质量指标导致高覆盖低断言质量套件——数字好看,套件啥也不抓,到生产才发觉。
最佳实践
- ✓把覆盖限定到业务逻辑(src/lib、src/services)排除配置、入口点、类型定义——要紧的数字是能有 bug 代码的覆盖。
- ✓用分支覆盖 作主指标非行覆盖——分支揭示 if/else 和 switch 缺口,边界 bug 藏处,行覆盖藏它们。
- ✓把未覆盖分支报告当风险地图审查:业务逻辑中每个红分支是 bug 可藏处,下一条测试应瞄准最高风险红分支。
- ✓覆盖率配变异测试得真质量信号——覆盖说"这行跑了",变异说"若这行错测试会发觉",只有后者意味测过。