文章

测试策略设计深度解析

测试策略设计的核心:测试金字塔的现代演化、覆盖率目标的正确设定、各层级分工与成本权衡。

测试策略设计深度解析

一句话概括

测试策略要回答的不是”用什么框架”,而是”在什么层级、花多少成本、测什么内容“——这是一道投入产出比(ROI)的权衡题。核心框架是测试金字塔:底层大量快速便宜的单元测试、中间适量组件/集成测试、塔尖少量慢而贵的 E2E 测试。策略设计的本质是:把有限的测试预算,分配到最能抓 bug、成本最低的层级上,同时用覆盖率门槛和CI 门禁把质量要求固化下来。

核心知识点

1. 测试金字塔:三层分工与成本权衡

金字塔的核心逻辑是越往上层越接近真实用户,但也越慢、越贵、越脆:

1
2
3
4
5
6
7
          /\
         /E2E\      ← 少量:验证核心业务链路,慢、贵、脆
        /------\
       / 集成测试 \   ← 适量:验证模块协同、边界
      /----------\
     /   单元测试  \  ← 大量:验证单个函数/组件逻辑,快、便宜、稳定
    /--------------\
  • 单元测试(底部):最快、最稳定、定位 bug 最准。应占最大比例。
  • 组件/集成测试(中部):验证组件渲染、模块间协同,抓单元测试覆盖不到的集成问题。
  • E2E(塔尖):真实浏览器走完整链路,抓系统级问题,但维护成本最高,数量要克制。

反模式是”冰淇淋蛋筒“(倒金字塔)——E2E 一大堆、单元测试几乎没有。这样的测试套件又慢又 flaky,维护成本爆炸,最终团队会放弃测试。

2. 覆盖率目标:设门槛,但不迷信数字

覆盖率门槛应该写进配置,在 CI 里硬性拦截:

1
2
3
4
5
6
7
8
9
// jest.config.js
coverageThreshold: {
  global: {
    branches: 80,     // 分支覆盖:每个 if/else 都要测
    functions: 85,
    lines: 85,
    statements: 85
  }
}

但要清醒两点:① 分支覆盖比行覆盖重要——if 的两个分支都测到才算数,纯行覆盖可以用 expect(1).toBe(1) 刷出来;② 覆盖率是下限不是目标——100% 覆盖不代表没 bug,它只保证”代码被跑到过”,不保证”行为被验证对”。设门槛是为了防止覆盖率滑坡,不是追求满分。

3. 各层级测什么:边界要划清楚

分层的意义在于每层只测它该测的,不越界重复:

层级测什么不测什么
单元测试纯函数逻辑、边界条件、错误分支跨模块交互、DOM 渲染
组件测试渲染结果、交互响应后端、数据库、真实网络
E2E完整用户流程、系统协同每个函数的分支细节

重复覆盖是浪费。单元测试已经把”空输入抛错”测透了,E2E 就没必要再跑一遍空输入场景——E2E 该专注”用户真的能走通这条路”。

4. 测试替身(Test Double)策略

替身是隔离依赖的工具,但每种用途不同,混用会埋坑:

1
2
3
4
5
6
7
8
9
10
// Stub:返回预设值,控制被测代码的输入
jest.mock('./api', () => ({ fetchUser: jest.fn().mockResolvedValue({ id: 1 }) }));

// Spy:不改变行为,只记录调用(验证"确实调用了")
const spy = jest.spyOn(console, 'log');
fn();
expect(spy).toHaveBeenCalledWith('called');

// Mock:既控制返回值又验证调用(stub + spy 的结合)
const mock = jest.fn().mockReturnValue(42);

策略要点:能 Stub 就不 Mock(保持被测逻辑真实)、Mock 边界不 Mock 被测逻辑、替身要在每个测试里重置(jest.clearAllMocks())避免用例间相互污染。

5. CI/CD 中的测试策略

策略最终要靠 CI 落地成门禁:

1
2
3
4
# 分层跑,按成本和反馈速度决定触发时机
# 1. 每次 commit:跑单元测试(快,秒级反馈)
# 2. 每次 PR:单元 + 组件测试(分钟级)
# 3. 合并主干/定时任务:E2E 全量(慢,小时级)

设计原则:

  • 快测试前置:单元测试最先跑,挂了立即 fail,不浪费 CI 资源跑慢的 E2E
  • 按变更触发:改了哪个包就跑哪个包的测试(monorepo 里尤其重要)
  • flaky 隔离:偶尔红的测试要标记、重试、修复,别让 flaky 侵蚀团队对测试的信任
  • 覆盖率上报:每次跑完把覆盖率 diff 到主分支,拦住”覆盖率下降”的 PR

其实你每天都在用

  • 合代码前的 CI 红灯:你 push 一个 PR,CI 先跑单元测试再跑组件测试,任何一层红了都合不进去,这就是分层策略在守门
  • 改动后只跑相关测试:你只改了登录模块,CI 只跑登录相关的测试文件而不是全量,省下大量时间,靠的是”按变更触发”策略
  • README 上的覆盖率徽章:那个”coverage 85%”的数字,是 CI 每次跑完测试统计并 diff 出来的,掉到 85 以下 PR 就被拦
  • E2E 只在发版前跑:日常开发不跑那条半小时的端到端,只有合并到 release 分支才触发,是”按成本分阶段”的典型
  • flaky 测试重跑按钮:GitHub Actions 上那个”Re-run failed jobs”,很多时候点的就是被网络抖动搞红的 E2E,而不是真 bug

常见误解(FAQ)

❌ 误区一:”测试越多越好,每层都覆盖到极致”

测试是有成本的负债——每条测试都要维护,改需求时测试也要跟着改。过度测试(尤其 E2E 堆太多)会拖慢 CI、制造 flaky,最终让团队对测试失去信心甚至删掉测试。正确目标是”关键路径高覆盖 + 成本合理“,不是数量竞赛。

❌ 误区二:”覆盖率 100% 就代表质量好”

覆盖率只说明”代码被跑到过”,不说明”行为被验证对”。expect(true).toBe(true) 能刷满行覆盖却抓不到任何 bug。质量的真指标是分支覆盖 + 边界条件 + 断言有效性。100% 覆盖率的烂断言,不如 70% 覆盖率的精准测试。

❌ 误区三:”单元测试、组件测试、E2E 测的东西都差不多,选一个就行”

三层测的是不同维度的问题,谁也不能替代谁:单元测试抓逻辑错误、组件测试抓渲染和交互、E2E 抓系统集成。只做 E2E 会漏掉大量边界分支;只做单元测试会漏掉”前后端接口对不上”这类集成 bug。三层是互补,不是冗余。

❌ 误区四:”测试策略定一次就不用管了”

策略要随项目演化持续调整:项目早期快速试错,可能只测核心逻辑;业务稳定后逐渐加组件测试和 E2E;发现某模块 bug 频发,就针对性补强该模块的测试。好的策略是活文档,定期复盘”哪里 bug 多、哪层测试没兜住”,反过来修正策略。

一句话总结

测试策略的本质是用最低的成本,在正确的层级,守住最关键的质量底线——金字塔分好层、覆盖率设门槛、CI 当门禁,测试才不会沦为”跑得很慢又没人看的摆设”,而是团队真正的安全网。

本文由作者按照 CC BY 4.0 进行授权