金字塔长啥样
- 底层:单元测试:多、快、稳,覆盖函数和模块;
- 中层:集成测试:验证模块协作、数据库与接口;
- 顶层:端到端:少而精,验证关键用户路径。
倒过来会怎样
一堆端到端测试:跑得慢、动不动因为环境或动画失败、出问题难定位。把多数断言下沉到单测,反馈更快。
两个提醒
- 别追求 100% 覆盖:关键逻辑和边界优先,胶水代码意义不大;
- 测试也要可维护:脆弱的测试比没测试更烦,断言要针对行为而非实现细节。
实战案例:三种失衡的测试结构
- 倒金字塔:大量端到端用例、极少单元测试,跑一次要半小时且常因环境波动失败,团队逐渐不再信任测试结果。
- “覆盖率表演”:为提升数字给 getter 写断言,关键分支仍无覆盖。应关注分支覆盖与边界用例(空值、极值、并发)。
- 测试依赖真实外部服务:支付、短信在 CI 里不可控,随机失败。用契约测试或打桩隔离外部依赖,只留少量集成环境验证真实调用。
常见问题(FAQ)
金字塔是硬性比例吗?不是,它是成本与速度的指引:单元测试最快最便宜,端到端最慢最脆,应少而精。要追求 100% 覆盖率吗?不必,覆盖率是下限而非目标,关键是覆盖高风险与复杂分支。什么时候写测试最划算?修 Bug 时先写复现用例再修;新增核心逻辑时同步补测。测试可以和生产代码分开改吗?应当一起改,改了行为却不更新测试,等于测试名存实亡。
测试数据与环境治理
测试写得再好,数据与环境混乱也会让结果不可信:
- 每个用例自造数据:用工厂函数或夹具生成所需数据,不依赖共享的“测试库固定数据”,否则用例之间会互相影响、无法并行;
- 数据隔离策略:单元测试用内存构造,集成测试用独立的 schema 或数据库实例,避免与开发库混用;
- 时间可注入:把“当前时间”做成可注入依赖,而不是直接调用系统时间,否则跨月、跨年与夏令时的用例会周期性失败;
- 清理与幂等:用例结束应能清理自己创建的数据;把用例设计成可重复执行,中途失败也不会污染下一次运行;
- 环境一致性:本地、CI 与预发尽量使用同一份依赖版本与配置模板,环境差异是“本地能过、CI 挂掉”的头号原因。
此外,测试也要有可观测性:失败的用例应输出足够定位的信息(输入、期望、实际、请求 ID),而不是只有“断言失败”四个字。
让测试真正被信任
- 失败必须可解释:用例失败时应能立刻看出是期望值不对、环境问题还是被测代码有缺陷。输出只写“断言失败”会让团队逐渐忽略失败信号。
- 删除长期失败的用例:长期被跳过或标记为已知失败的用例,会训练团队习惯性忽略结果。应修复或明确删除,并在跟踪系统中记录原因。
- 控制执行时间:单元测试应在秒级完成,超出则说明依赖过重。慢测试会被排除在日常流程之外,最终只在发布前运行一次。
- 避免测试之间的顺序依赖:用例应可单独运行与并行运行,顺序依赖会让失败难以复现,也会让增量运行无法使用。
- 把回归测试绑定到缺陷:每修一个缺陷都补一个能复现它的用例,长期积累下来,测试集就成了一份精确的已知问题清单。
测试的价值等于“团队是否愿意依据它的结果做决策”。所有提升可读性、速度与稳定性的工作,最终都是为了让这个前提成立。