flaky 测试为什么要单独对待
不稳定测试(flaky test)指同一份代码在相同环境下时通时不过。它最大的损失不是某一次失败,而是信任被侵蚀——红了就重跑变成习惯,真正的失败也会被当成噪声忽略。评审新增或修改测试时,值得专门看一眼常见的不稳定来源。
基本信息
| 表现 | 同一提交上时通过时失败,重跑往往又变绿 |
|---|---|
| 常见来源 | 时间依赖、随机数据、共享状态、外部依赖、并行竞争 |
| 评审动作 | 识别来源并要求修复,而不是靠重试掩盖 |
| 处置顺序 | 先定位不稳定来源,再决定修测试还是修被测代码 |
典型使用场景
- 测试里有 sleep 或真实时钟:改成可注入的时钟,不依赖执行速度
- 用例使用随机数据:固定随机种子,失败时输出可复现的信息
- 用例之间共享数据库或全局状态:改为各自创建并清理数据
- 依赖外部网络与第三方服务:用桩件或本地假实现替代
- CI 上比本地更容易失败:多为并行执行与资源竞争,需要隔离
不稳定来源与修复方向
| 来源 | 典型写法 | 修复方向 |
|---|---|---|
| 时间依赖 | 先 sleep 一秒再断言 | 注入可控时钟,或等待明确条件而不是等待时间 |
| 随机数据 | 随机生成的手机号与昵称 | 固定种子,并把种子打进失败日志 |
| 共享状态 | 多个用例共用同一测试用户 | 每个用例创建并清理自己的数据 |
| 外部依赖 | 直接调用真实的支付网关 | 改用桩件或本地假实现,契约另行验证 |
| 超时过短 | 默认超时在慢机器上必然失败 | 按最慢环境的实际耗时给足超时 |
本地复现与定位
本地复现与定位
# 反复运行同一个用例,估一下失败率
for i in $(seq 1 20); do pytest tests/test_order.py::test_retry -q || echo "failed at $i"; done
pytest -p no:randomly tests/ # 关闭随机顺序插件,验证是否与执行顺序相关
pytest --tb=long -x tests/test_order.py # 保留完整堆栈
# CI 侧开启失败日志与截图上传,便于比对偶发失败的现场用重试掩盖不稳定测试:失败率下降但根因仍在,还会持续掩盖真实存在的偶发缺陷。
给已知的不稳定测试打标签并记录失败率,比口口相传可靠:治理清单有了数据才好排优先级。