1546 字
约 5 分钟
13
S16-B01 · 测试的层次与判据:单测为什么"全绿"还会出事故
2024-12-31
2026-11-07

适用读者:能写测试但说不清"该写哪一层"的开发;被"覆盖率 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、事务、序列化 完整用户路径、部署形态
端到端 少 十秒–分钟 系统级行为、真实依赖 分支覆盖(成本不允许)

两条被误读的结论:

  1. "E2E 要少"≠"E2E 可省"。少的是数量,不是存在性;关键路径必须有 E2E,否则"系统能不能用"无人证明。
  2. "单测要多"≠"覆盖率要高"。覆盖率衡量"代码被执行",不衡量"行为被断言";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. 自测

  1. 你最近一次线上事故,能在哪一层测出来?如果答案是"哪层都测不出",说明缺的是可观测性(J01 起)。
  2. 你的项目里有没有"只有 mock 通过"的用例?把它们标出来,改成集成用例。
  3. 用 §3.1 的表,为你的核心接口各写一行:证明什么、用哪层、判据是什么。

7. 下一篇

S16-B02 · 单元测试:JUnit 5 + Mockito 的最小可运行 —— 从依赖到运行输出,讲清 AAA、断言粒度,以及"测到"与"跑过"的区别。

S16-B01 · 测试的层次与判据:单测为什么"全绿"还会出事故
https://www.clxhxhhr.top/posts/5406/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。