53.1 测试金字塔
一句话
测试按 “单元 → 集成 → 端到端” 分层,越往下越多、越快、越稳;越往上越少、越慢、越脆。
金字塔长什么样
▲ 端到端(E2E) ← 少、慢、贵、脆
▲▲ 集成测试 ← 中
▲▲▲ 单元测试 ← 多、快、便宜、稳
▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲▲
三层各测什么、各有什么代价
| 层 | 测什么 | 速度 | 单次成本 | 稳定性 | 定位能力 | 经验占比 |
|---|---|---|---|---|---|---|
| 单元测试 | 单个函数 / 方法的行为 | 毫秒级 | 极低 | 高 | 精确到函数 | ~70% |
| 集成测试 | 模块之间能否合得上 | 秒级 | 中 | 中 | 精确到"哪两个模块" | ~20% |
| 端到端测试 | 整个系统一条链路 | 分钟级 | 高 | 低(天生 flaky) | 只能告诉你"有 bug" | ~10% |
为什么必须是金字塔,不能倒过来
三个原因,全是 “越往上越糟糕”:
- 速度:单元测试毫秒级,E2E 分钟级。测试跑得越慢,人越不想跑,最终会绕过它。
- 定位:E2E 挂了,你不知道是前端、后端、数据库还是网络的问题;单元测试挂了,直接指向某个函数。
- 脆弱 (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):先给 “即将修改的那段代码” 拍一张 “当前行为快照”,再改。不要在没需求的地方大规模补测——投入产出比最低,且容易变成 “测了实现细节”。