| 名称 | 不稳定测试归因与治理 |
| 描述 | 当测试间歇性失败、持续集成红绿灯不稳定或需要建立隔离与治理机制时使用。该技能按六类根因(时序、共享状态、随机数据、外部依赖、并发、资源泄漏)定位不稳定测试,给出复现脚本、修复优先级与隔离策略。适用于单元测试、集成测试、端到端测试与微服务契约测试。关键词:不稳定测试、间歇失败、持续集成失败、测试隔离、时序问题、竞态条件、重试策略、测试治理、根因分析、测试可靠性、pytest、JUnit、Jest。 |
不稳定测试归因与治理
把“重跑就过”的测试变成“要么稳定通过、要么暴露真实缺陷”。
何时使用
- 同一提交多次运行结果不同。
- 持续集成(CI)中固定几个用例偶发失败。
- 团队开始用“重试三次”掩盖问题。
- 需要制定测试隔离与配额机制。
不适用:稳定失败的用例(那是普通缺陷);性能测试的抖动(另用基准方法)。
输入
- 失败用例名、失败率、最近 N 次失败日志。
- 运行环境:本地/持续集成、并发度、机器规格。
- 测试类型:单元、集成、端到端。
- 是否依赖网络、数据库、时间、随机数。
失败率低(<1%)时,先用重复运行放大问题,不要直接读日志猜。
工作流
第 1 步:放大复现
- 单用例重复运行 100 次,记录失败分布。
- 提高并发度与降低机器性能,触发资源竞争。
- 固定随机种子再跑一遍,判断是否与随机有关。
复现命令示例:
pytest tests/test_order.py::test_settle -q --count=100 --randomly-seed=1234
第 2 步:六类归因
按 references/triage-taxonomy.md 对照日志特征:
| 类型 | 典型信号 | 常见根因 |
|---|---|---|
| 时序 | sleep 后偶发、断言超时 | 异步未等待、定时器精度 |
| 共享状态 | 单独跑过、连同跑失败 | 全局缓存、单例、数据库残留 |
| 随机数据 | 特定输入才失败 | 随机 ID、时区、哈希顺序 |
| 外部依赖 | 网络抖动、第三方限流 | 未打桩、真实调用 |
| 并发 | 高并发才失败 | 锁顺序、非线程安全容器 |
| 资源泄漏 | 后期用例变慢或内存耗尽(OOM) | 连接未关、临时文件堆积 |
第 3 步:定位到代码
- 给失败断言加上下文:实际值、时间戳、线程名、请求 ID。
- 用时序日志确认事件顺序,而不是靠猜。
- 二分法禁用可疑测试夹具(fixture)或并行度。
修复方向优先选“消除不确定性”(改为确定性等待、注入时钟、隔离数据),而不是“加大超时”。
第 4 步:隔离与治理
- 隔离区:标记
flaky,不阻塞主干,但每日定时跑并计入看板。 - 配额:单仓库隔离用例不超过 1%,超限冻结新增。
- 责任:每个隔离用例必须有负责人与修复期限(建议 10 个工作日)。
- 准入:新增用例需连续 50 次通过才进入主干套件。
第 5 步:验证修复
- 修复后连续运行 200 次无失败。
- 在持续集成最低配机器上验证。
- 若根因是产品竞态,补一条确定性回归测试,而不是只改测试。
检查清单
- [ ] 已量化失败率,而非“偶尔失败”。
- [ ] 已确认是测试问题还是产品缺陷。
- [ ] 修复针对根因,不是简单延长超时。
- [ ] 随机数与时间已注入或固定。
- [ ] 测试间数据隔离,无残留。
- [ ] 外部依赖已打桩或使用本地容器。
- [ ] 隔离用例有负责人与期限。
- [ ] 修复后在低配环境重复验证。
输出模板
## 结论
测试问题 / 产品缺陷 / 环境问题
## 证据
- 失败率:
- 复现命令:
- 关键日志:
## 根因
...
## 修复
...
## 验证
- 连续运行次数:
- 低配环境结果:
## 治理动作
- 是否隔离:
- 负责人:
- 期限:
反模式
- 用全局重试掩盖问题,拉长反馈时间且掩盖真实缺陷。
- 把
sleep从 1 秒加到 5 秒。 - 只在本机验证,持续集成仍失败。
- 隔离后无人认领,隔离区无限膨胀。
- 删除难修的用例当作“修复”。
参考文件
references/triage-taxonomy.md:六类根因的日志特征与修复手法。references/quarantine-policy.md:隔离、配额与准入规则。