3198 字
约 10 分钟
17
S16-A04 · 测试分层策略:把预算花在哪一层
2024-12-31
2026-11-09

"测试写了一千多个,线上还是出事"——这通常不是写得不够,而是写得不是地方。单测一千个覆盖的是你自己写的分支,集成测试缺位意味着依赖之间的假设没人验,端到端又慢又脆所以没人跑,逃逸缺陷恰好从这三层的缝里漏过去。测试分层的本质是预算分配:每一层的反馈速度、稳定性、能证明什么,差着数量级。钱花错层,写多少都不够。


1. 四层的成本曲线

层 单次反馈时间(典型量级) 稳定性 能证明什么 单次维护成本
单元测试 毫秒级 高,无外部状态 分支与边界逻辑正确 低,改实现才动
集成测试 秒级到十秒级 中,受容器/网络影响 组件间的协议与事务真实成立 中,依赖版本变动即动
契约测试 十秒级 高,双方各自跑 提供方与消费方对接口的理解一致 中,接口变更是触发器
端到端 分钟级 低,最易 flaky 关键路径在真实拓扑下走得通 高,环境一变就修

这张表的方向性结论很稳定:越往上,能证明的事越接近用户,但反馈越慢、越脆、越贵。所以分层不是"每层都写一点",而是"每一类风险交给最便宜的那一层去证明"。

2. 比例不能套公式

流传的"70/20/10"只是某类单体 Web 应用的形态快照,不是定律。分布应该由系统形态和变更热点推出来:

判断顺序
1. 变更热点在哪   git 历史里改动最频繁的目录 → 该处单测密度最高
2. 哪些假设最脆   跨进程、跨团队、跨存储的假设 → 集成/契约覆盖
3. 哪些路径不可逆 支付、删除、发信、发布 → 端到端兜一条
4. 剩下的         不写。没有风险的代码不配预算
系统形态 应加重的层 理由
纯计算/算法库 单元测试 无依赖,纯函数最便宜
单体 + 单库 单元 + 少量集成 事务边界在进程内可测
多服务 + 多团队 契约测试 接口演进是主要故障源
强前端交互 组件测试 + 少量端到端 DOM 断言比全链路快一个量级
数据管道 集成 + 数据校验 逻辑常藏在 SQL 与调度里

3. 测试能保护什么,保护不了什么

能保护 机制 保护不了 为什么
回归 已有行为被改动时报警 需求是否正确 测试只验证"按写的跑"
设计反馈 难测=耦合,逼你拆边界 性能与容量 单次执行证明不了分布
活文档 用例即行为说明 UI 感受与可用性 断言不了"顺手"
重构安全网 结构改动后行为不变 数据迁移正确性 需真实数据才有意义

把测试当"质量证明"是分层策略里最贵的误解:它证明的是你写下的假设仍然成立,不是产品是对的。需求错了,测试会稳定地保护这个错误。

4. 集成测试:真实依赖还是替身

选择 得到 失去 适用
真实依赖(容器起库/中间件) 方言、事务、序列化真实 启动慢、环境漂移、端口冲突 事务边界、SQL、消息语义
替身(内存实现/mock) 快、可控、易造异常 真实行为差异被隐藏 分支与错误处理路径
录制回放 真实流量形态 过期流量会误导 外部第三方接口

关键判据只有一句:被替身替换掉的那部分,正是这个测试想证明的东西吗? 如果测试目的是"SQL 写对了",用内存库替身等于什么都没测;如果目的是"超时分支怎么处理",真实库反而是噪声。

5. 契约测试解决的是接口演进

# consumer 侧声明自己真正用到的东西
consumer: order-service
provider: inventory-service
interactions:
  - request:  { method: GET, path: /stock/SKU-1 }
    response:
      status: 200
      body: { available: true, reserved: 0 }

契约测试的价值不在断言,而在把"消费方实际依赖的字段"变成可执行文件。提供方改字段时,CI 立刻知道谁会被打断。它解决不了的是:契约没写的隐式约定(时区、编码、空值语义),这些仍需集成测试兜。

实施成本主要在组织侧:契约要有人维护、要能定位归属、要有版本对应关系。跨团队推不动时,往往不是工具问题,而是没人愿意为"别人的构建"负责。

6. 端到端只该覆盖不可逆与最关键

端到端的合理范围很小:

该覆盖 不该覆盖
支付/扣款/退款 每一个表单校验分支
注册 → 首单 → 收货 各种错误提示文案
发布/部署后的冒烟 分页、排序、筛选组合
权限越界的少数关键组合 全部角色 × 全部资源矩阵

判据是出错后能否用钱或时间修回来。可逆的错误交给下层;不可逆的才值得付端到端的分钟级代价。端到端套件超过二十条,通常已经在替集成测试干活,且干得更差。

7. 测试数据的三种策略

# 工厂:显式构造,只声明本用例关心的字段
order = OrderFactory.create(status="PAID", amount=Decimal("99.00"))
# 其余字段由默认值兜底,读用例时不必理解十个无关参数
策略 优点 坑
固定夹具(fixture 文件) 稳定、可 review 与 schema 脱节后变成假数据
工厂/构造器 用例意图清晰、易变体 默认值漂移会让断言变脆
生产快照脱敏 分布真实,能撞出脏数据问题 脱敏规则漏项、快照过期、体积大

生产快照最大的风险不是隐私,是你以为它代表现在。快照一旦变成"上次导出的那份",它测的就是历史。用它时必须配一个"快照年龄"检查。

8. 不稳定测试:隔离,而不是重试

# CI 里重试只允许作为观测手段,不能作为通过手段
test:
  retries: 0
  quarantine:
    label: flaky
    action: exclude_from_gate   # 移出准入门禁,单独跑并统计
    owner_required: true
根因类别 表现 处理
时间依赖 跨零点、时区、sleep 断言 注入时钟,禁用真实等待
顺序依赖 单跑过、全跑挂 每例自建状态,禁止共享可变夹具
并发/端口 随机失败、本机不复现 动态端口、串行化共享资源
外部服务 网络抖动 该层改用替身或录制回放

重试是通过率的美容术:它把 3% 的真实缺陷藏成 0.1% 的日志噪声。治理顺序应该是先分类,再决定留不留——分类不了的 flaky 测试,价值通常低于它的维护成本。

9. 覆盖率的正确用法

覆盖率是发现盲区的探照灯,不是质量指标。行覆盖高但断言为空,等于执行过没验证过;分支覆盖能暴露"异常分支从未走过",这才是它真正有用的地方。

用法 有效 失效
找从未执行的代码 有效,指向死代码或漏测 —
当团队 KPI — 会催生无断言测试与 assertNotNull
卡合并门禁 对新代码有效 全量阈值会被历史代码拖死
比较两个模块 参考 业务复杂度不同,不可比

一旦覆盖率进考核,它衡量的就是"测试代码行数",而不是风险覆盖。这个转变通常在一个季度内发生。

10. 与技术债、发布节奏的关系

变更热点优先,是补测试债唯一划算的顺序:

# 近半年改动最多的文件,测试债该按这个顺序还
git log --since="6 months ago" --name-only --pretty=format: \
  | grep -v '^$' | sort | uniq -c | sort -rn | head -20
发布节奏 测试投入的回收方式
日更多次 单测与契约先行,端到端只做冒烟
周更 集成测试可在合并前跑全量
月更/季度 端到端可以更重,人工回归仍占一部分
强监管发布 证据链优先,测试即审计材料

快速迭代下,"补全测试"往往永远排不上;而"改这个文件前先补它的测试"能排上。差别在于后者绑定了一次真实变更。

11. 可度量

指标 口径 用途
逃逸缺陷率 上线后发现 ÷ 总缺陷 分层是否有洞
测试套件时长 PR 门禁 P50/P95 超阈值就开始被绕过
缺陷平均修复时间 从报警到恢复 可观测与测试的共同结果
flaky 比例 非确定性失败 ÷ 总运行 高于个位数就该治理

套件时长有一个隐性阈值:超过开发者愿意等的时长,他们就开始本地跳过、CI 拆分、合并时抢跑。这个阈值因团队而异,但被绕过之后,覆盖率再高都没意义。

12. 排错表

现象 常见原因 处理
单测全绿、联调就挂 依赖用替身,真实行为没验 关键边界补集成测试
契约测试通过但线上 500 契约只覆盖显式字段 补隐式约定(空值、时区)
端到端经常红,重跑就绿 时序/共享环境 隔离数据、动态端口、去 sleep
覆盖率 85% 仍频繁出事 断言稀薄、只覆盖主路径 改看分支覆盖与逃逸缺陷
没人愿意写测试 套件太慢、夹具难用 先砍时长,再谈覆盖率

13. 边界

情形 判断 理由
一次性脚本/迁移工具 不写单测,写校验脚本 生命周期短于测试维护期
原型/探索阶段 只写关键断言 需求还在变,测试会先死
强依赖外部系统 录制回放 + 人工验证 无法控制对端行为
纯 UI 观感 截图比对 + 人看 断言不了"好不好看"
遗留系统无测试 只在新改动处补 全量补测的回收期过长

分层策略最终要回答一个问题:这次改动最可能错在哪,哪一层能用最小代价发现它。答不上来时,多写测试只是把不确定性往后推。

14. 自测

  1. 你的端到端套件里,有几条是"不可逆操作"?其余的在替谁干活?
  2. 集成测试用替身的那部分,是否正好是它想证明的东西?
  3. 契约里没写的隐式约定(空值、时区、编码),现在由哪一层兜着?
  4. 套件时长超过多久,你的团队就开始绕过门禁?
  5. 覆盖率如果明天变成 KPI,你的测试会怎么变形?
  6. 上一次逃逸缺陷,本该被哪一层拦住?为什么没拦住?
S16-A04 · 测试分层策略:把预算花在哪一层
https://www.clxhxhhr.top/posts/6097/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。