1546 字
约 5 分钟
13
S16-B01 · 测试的层次与判据:单测为什么"全绿"还会出事故
适用读者:能写测试但说不清"该写哪一层"的开发;被"覆盖率 80%"安慰过的人。
前置:S04(Java 基础)、能跑./gradlew test。
覆盖旧文:A-275 → 22 点(§2 单元测试的盲区、§3 测试金字塔);另见 A-1881(测试方法论书目,见 X01)。
扩展点:E-S16-001(测试层次判据表)。
下一篇:S16-B02 · 单元测试的最小可运行。
1. 一句话结论
测试的层次不是"越多越好",而是"越靠近故障形态越好"。 单元测试擅长证明逻辑,集成测试证明契约,端到端证明系统;用单元测试去证明系统行为,注定漏。
| 反例(原文 A-275) | 结果 |
|---|---|
| Electron 导出功能:渲染组件、预加载脚本、服务层各自单测全绿 | 实际点击导出:路径格式错、进度条无反应、大文件内存泄漏,5 个组件边界缺陷,单测一个都没发现 |
2. 单元测试的盲区(§2)
原文的判断很直接:单元测试的设计哲学是"隔离"——mock 依赖、专注被测单元。这使它快而准,也制造了系统性盲区。
| 盲区 | 机制 | 典型表现 |
|---|---|---|
| 接口不匹配 | 上下游各自 mock,都按自己的假设通过 | 渲染进程传相对路径,预加载脚本要绝对路径 |
| 状态传播错误 | 每次测试都是全新 mock,跨层缓存不会脏 | 迁移改了表结构,ORM 缓存仍持旧结构 |
| 资源生命周期 | 测试各自创建/销毁资源,不共享 | 文件句柄、连接、socket 泄漏只在长跑时暴露 |
| 环境依赖性 | 测试环境全 mock,真实环境有配置/网络/服务差异 | 本地全绿,预发因 DNS 或证书失败 |
怎么判断"这个缺陷单测能不能测到":问一句——它需要两个真实组件同时在场吗? 需要 → 单测测不到,别在那里堆用例。
3. 测试金字塔的正确读法(§3)
原文引用的金字塔(Mike Cohn 提出,Google 测试博客长期沿用)是成本模型:
| 层 | 数量 | 速度 | 能证明 | 不能证明 |
|---|---|---|---|---|
| 单元 | 多 | 毫秒 | 分支逻辑、边界条件 | 组件间契约、序列化、事务 |
| 集成 | 中 | 秒 | 契约、SQL、事务、序列化 | 完整用户路径、部署形态 |
| 端到端 | 少 | 十秒–分钟 | 系统级行为、真实依赖 | 分支覆盖(成本不允许) |
两条被误读的结论:
- "E2E 要少"≠"E2E 可省"。少的是数量,不是存在性;关键路径必须有 E2E,否则"系统能不能用"无人证明。
- "单测要多"≠"覆盖率要高"。覆盖率衡量"代码被执行",不衡量"行为被断言";
assertEquals(1,1)也能拉覆盖率。
3.1 判据表(E-S16-001)
| 你要证明的事 | 该用哪层 | 判据(怎么知道做对了) |
|---|---|---|
| 价格计算在边界值正确 | 单元 | 边界三连:下界、上界、越界各一条断言 |
| DAO 的 SQL 在 MySQL 8 上正确 | 集成(真库/容器) | 同一用例在 H2 与 MySQL 上结果一致(或在 H2 上显式标注不适用) |
| 下单接口的鉴权与事务 | 集成(切片) | 未登录 401、越权 403、异常回滚后无脏数据 |
| 用户能完成"下单→支付→查订单" | 端到端 | 一条脚本跑通三步,且中途不断言 mock |
| 服务在依赖超时时降级 | 集成 + 混沌 | 注入 3s 延迟,响应时间 < 1s 且返回兜底值 |
4. 一个最小例子:同一个 bug,三层各测一遍
// 业务:下单扣库存,库存不足抛异常
public class InventoryService {
public void deduct(long skuId, int qty) {
int left = repo.decrease(skuId, qty); // UPDATE ... SET stock = stock - ? WHERE stock >= ?
if (left < 0) throw new InsufficientStockException(skuId);
}
}
| 层 | 用例 | 能发现什么 | 发现不了什么 |
|---|---|---|---|
| 单元(mock repo) | repo.decrease 返回 -1 → 抛异常 |
分支逻辑 | SQL 的 WHERE stock >= ? 是否写对 |
| 集成(Testcontainers MySQL) | 库存 1 扣 2 → 抛异常且库存仍为 1 | SQL 条件、事务回滚 | 网关超时重试导致的重复扣减 |
| 端到端(HTTP) | 并发 20 个请求抢 5 件库存 | 超卖、幂等 | ——(这条只有 E2E 能证明) |
结论:三层不是替代关系,是递进的证据链。缺哪层,哪类缺陷就没有证据。
5. 常见误区
| 误区 | 纠正 |
|---|---|
| "单测覆盖率 80% 就能上线" | 覆盖率是下限指标,不是质量指标;关键路径要单独列出并逐条有 E2E |
| "mock 掉数据库更快" | 快,但把 SQL/事务/类型映射整块排除在证据之外 |
| "E2E 太慢所以不写" | 慢所以要少而准:只覆盖主干路径与高危路径 |
| "测试是 QA 的事" | 交付者不证明自己的交付,等于把验证成本转嫁给线上 |
6. 自测
- 你最近一次线上事故,能在哪一层测出来?如果答案是"哪层都测不出",说明缺的是可观测性(J01 起)。
- 你的项目里有没有"只有 mock 通过"的用例?把它们标出来,改成集成用例。
- 用 §3.1 的表,为你的核心接口各写一行:证明什么、用哪层、判据是什么。
7. 下一篇
S16-B02 · 单元测试:JUnit 5 + Mockito 的最小可运行 —— 从依赖到运行输出,讲清 AAA、断言粒度,以及"测到"与"跑过"的区别。
S16-B01 · 测试的层次与判据:单测为什么"全绿"还会出事故
https://www.clxhxhhr.top/posts/5406/ 评论
0 条
还没有评论,先写一条吧。