人人都会AI编程

20.3 测试覆盖率指标与测试用例设计原则

更新时间:2026-07-11

测试覆盖率是衡量“代码被测试执行了多少”的量化指标,它能帮你发现哪些逻辑从未被验证过。但覆盖率本身不是目标:100% 覆盖率不代表程序没 bug,0% 覆盖率也不代表功能不可用。它只是一个指导工具,告诉你测试的薄弱区域在哪里。

常见的覆盖率指标

现代测试工具(如 Vitest 内置的 c8/istanbul)通常会输出以下几项指标:

| 指标 | 含义 | 实用理解 |
|------|------|----------|
| 语句覆盖率 (Statement) | 所有可执行语句中被执行的比例。 | 最基础的指标,一条 console.log(...) 就算一句。 |
| 分支覆盖率 (Branch) | 所有条件分支(if/else、switch/case、三元运算符)中被执行的比例。 | 重点看这个。只执行了 if 没走 else,分支覆盖率就只有一半。 |
| 函数覆盖率 (Function) | 所有函数被调用的比例。 | 某个工具函数导出但从未在测试中被调用过,这里会暴露。 |
| 行覆盖率 (Line) | 所有源代码行中被执行的比例,与语句覆盖率类似,但以行为单位。 | 一行可能含多个语句,但通常语句和行覆盖率结果接近。 |

对于 Vue 项目来说,这些指标的实用价值排序通常是:分支覆盖率 > 函数覆盖率 > 语句覆盖率。因为分支决定了逻辑的不同走向,而函数覆盖率能快速暴露“写了但没测的工具函数”。

如何看待覆盖率数字

  • 实用目标:对业务组件而言,分支覆盖率保持在 70%~85% 之间是性价比最高的区间。核心逻辑(如权限判断、数据转换、支付计算)尽量做到 90% 以上,纯渲染的模板部分不强求。
  • 不要迷信 100%:为了凑覆盖率而写的“无效测试”(比如只触发函数但不验证返回值)只是浪费 CI 时间,还会给后续维护造成“假安全感”。
  • 读报告而非盯数字:关键要看报告中红色的未覆盖分支。如果未覆盖分支是你认为不可能发生的边界条件,可以加个注释忽略;如果是遗忘的异常处理路径,就补测试。

测试用例设计的核心原则

好的测试用例不是东一榔头西一棒子地乱写,而是遵循一些经过验证的设计思路。

1. 先覆盖“Happy Path”核心流程
先保证正常流程能跑通。一个表单组件,最基本的用例就是:填写合法数据 → 提交 → 验证成功回调被触发。正常流程都跑不通,测边界情况没有意义。

2. 边界与极端值必须覆盖
大多数 bug 都藏在边界。对函数来说,传入 nullundefined、空字符串、超长字符串、负数、数组越界索引等;对组件来说,Props 传入空数据、列表为空、Loading 状态、报错状态都需要有对应用例。

// 示例:测试一个格式化金额的函数
test('传入 0 应返回 "0.00"', () => {
  expect(formatMoney(0)).toBe('0.00')
})
test('传入负数应返回带负号的字符串', () => {
  expect(formatMoney(-100)).toBe('-100.00')
})
test('传入非法字符串应抛出异常', () => {
  expect(() => formatMoney('abc')).toThrow()
})

3. 每个用例只测一件事
避免在一个用例里塞进多个断言,尤其是前一个断言的失败会阻塞后续断言执行时。用多条 testit,失败时能直接定位是哪个场景坏了。

4. 组件测试应以用户视角验证行为
对于 Vue 组件,不要直接断言 wrapper.vm.count,而是模拟用户点击按钮后,验证界面上渲染的内容是否变化。这样测试更接近真实使用,也避免内部实现一改就全量挂掉。

test('点击按钮后计数显示为 1', async () => {
  const wrapper = mount(Counter)
  expect(wrapper.text()).toContain('0')
  await wrapper.find('button').trigger('click')
  expect(wrapper.text()).toContain('1')
})

5. 异常处理必须有测试
如果代码里写了 try...catch 或者 Promise 的 .catch,那就一定要写触发这个分支的测试。否则这些错误处理逻辑只是装饰,上线后出现问题可能依然白屏。

6. 不测框架本身的行为
不需要测试 v-model 是否能更新数据,那是 Vue 自己的单测范围;也不需要验证 ref 是否返回响应式对象。只聚焦于你自己的业务逻辑、自定义 Hooks、组件交互和边界条件。

7. 复用测试工具函数,减少重复
把常用的挂载、Mock 操作抽成 helper,避免每个测试文件都重复写一遍 createWrappersetupPinia。保持测试代码同样遵循 DRY 原则。

实际落地建议

  • package.json 中配置覆盖率报告目录,CI 中生成报告但不强制门禁(除非团队已有成熟测试文化)。
  • 新建组件或工具函数时,至少写 2~3 个核心用例,不让测试债务累积。
  • 重构时优先补齐旧代码的边界测试,防止改坏。
  • 定期检查覆盖率报告,识别那些“从未被执行过的文件”,判断它是否真的需要留着。

测试覆盖率是一面镜子,不是一把标尺。它的意义在于帮你发现遗漏的测试场景,而不是给你一个可以炫耀的数字。把精力放在真正有效、能防止回归的用例上,才是 Vue 项目质量工程的务实之道。