53.1 测试金字塔

一句话

测试按 “单元 → 集成 → 端到端” 分层,越往下越多、越快、越稳;越往上越少、越慢、越脆。

金字塔长什么样

        ▲ 端到端(E2E)        ← 少、慢、贵、脆
       ▲▲ 集成测试            ← 中
      ▲▲▲ 单元测试            ← 多、快、便宜、稳
     ▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲

三层各测什么、各有什么代价

层 测什么 速度 单次成本 稳定性 定位能力 经验占比
单元测试 单个函数 / 方法的行为 毫秒级 极低 高 精确到函数 ~70%
集成测试 模块之间能否合得上 秒级 中 中 精确到"哪两个模块" ~20%
端到端测试 整个系统一条链路 分钟级 高 低(天生 flaky) 只能告诉你"有 bug" ~10%

为什么必须是金字塔,不能倒过来

三个原因,全是 “越往上越糟糕”:

  1. 速度:单元测试毫秒级,E2E 分钟级。测试跑得越慢,人越不想跑,最终会绕过它。
  2. 定位:E2E 挂了,你不知道是前端、后端、数据库还是网络的问题;单元测试挂了,直接指向某个函数。
  3. 脆弱 (flaky):E2E 依赖环境、网络、时序,时好时坏。flaky 的测试最终的命运是 “先注释掉,以后再说”——而这个 “以后” 永远不会来。

反模式:冰淇淋圆锥 (Ice Cream Cone)

        ▲▲▲▲▲▲▲▲▲▲ E2E(一大堆)
       ▲▲▲▲▲▲ 集成(中)
      ▲ 单元(几乎没有)
  • 症状:单元测试几乎为零,全靠 E2E 一条龙兜底。
  • 后果:CI 跑 40 分钟、天天红、红了没人查、最后全员 “跳过测试”。
  • 根因:图省事,觉得 “E2E 从头到尾都测了,还要单元测试干嘛”。

一条铁律

能在下层测的,绝不放上层。 一个逻辑 bug,单元测试 10 毫秒能定位;放到 E2E,要 5 分钟 + 一堆环境依赖,还只告诉你 “挂了”。

一个诚实的补充

金字塔是启发式,不是教条。现代也有不同声音——Kent C。Dodds 的 “测试奖杯 (Testing Trophy)” 主张把重心往上移到集成层,理由是 “过度隔离的单元测试给了虚假安全感”。**结论不变:E2E 永远是最少的那一层。**争论的只是 “单元和集成谁多一点”,不是 “要不要金字塔”。

落到数字:三层的时间与数量预算

层 单次耗时 数量占比 反馈预算
单元测试 0.1–10ms 60%–75% 每次提交 < 30s
集成测试 100ms–2s(起容器另算) 20%–30% 每次 PR < 5min
E2E 10–60s 5%–10% 合并前 < 15min
# CI 分阶段:先快后慢,失败即停
go test ./... -short            # 单元(毫秒级)
go test ./... -tags=integration # 集成(真实依赖)
go test ./e2e/... -count=1      # E2E(最后跑)

判据:任何一个阶段的耗时超过下一个人愿意等的阈值,测试就会被绕过——这才是金字塔存在的真正理由,不是 “理论优雅”。

自检

Q1 你的 CI 测试要跑 15 分钟。第一嫌疑是什么?

▸ 答案:E2E 占比失衡 (冰淇淋圆锥)。CI 超过 10 分钟,人会开始绕过测试;先砍 E2E、下沉到单元/集成,再谈别的优化。

Q2 “E2E 一条龙都测了,为什么还要单元测试?” 用一句话反驳。

▸ 答案:E2E 只能告诉你 “有 bug”,定位不到 “bug 在哪”,而且又慢又 flaky;单元测试又快又准。测试的目的不是 “证明系统能跑”,是 “快速定位问题”。 Q3 覆盖率 80% 能说明什么、不能说明什么?

▸ 答案:能说明 “80% 的代码被执行过”,不能说明断言有效:没有断言的测试也能刷覆盖率。覆盖率适合当 “下限报警”(某模块突然掉到 30%),不适合当质量目标;更重要的是 “关键路径是否有断言”。

Q4 为什么 E2E 的数量必须严格受限?

▸ 答案:因为它的成本是双重的:每次跑都要起完整环境(10–60s),而且 flaky 率高(网络、时序、数据)。E2E 多了,CI 变慢、结果不可信,团队会开始 “重试直到绿”——这时测试已经失去意义。

Q5 遗留系统没有测试,怎么开始建金字塔?

▸ 答案:从 “改动处” 加特征测试(characterization test):先给 “即将修改的那段代码” 拍一张 “当前行为快照”,再改。不要在没需求的地方大规模补测——投入产出比最低,且容易变成 “测了实现细节”。