Flaky 用例治理:把"时好时坏"从文化问题变成工程问题

“这个用例又挂了?应该不是真 bug 吧?”——当这句话在团队里成为常态,测试系统的公信力就死了。Flaky 用例的危害不是"多跑几次",而是训练出"红了也不信"的团队文化。这篇讲 flaky 治理的完整闭环:识别、量化、隔离、根因、消灭。

一、先定义:什么是 flaky(别把真 bug 当 flaky)

1
2
3
4
5
6
7
8
9
Flaky 判定标准(三条同时满足):
1. 同一代码 + 同一环境 + 同一数据,结果不稳定
2. 重跑后能通过(重跑通过率 0 < p < 1)
3. 无代码/环境/数据变更发生

反例(不是 flaky,是真问题):
- 环境被改过 → 环境稳定性问题(先对账环境指纹)
- 数据被污染 → 数据隔离问题
- 时序敏感但结果应该确定 → 这是"设计上的竞态 bug",修代码不是修用例

关键区分:flaky 是"测试系统的噪音",竞态 bug 是"产品的缺陷"。把竞态 bug 当 flaky 隔离掉,等于让产品缺陷隐身——判定时先问"这个不稳定是不是产品该有的行为"

二、量化:给 flaky 建监控(没有数据就没有治理)

1. flaky 识别机制(自动化,不靠人肉回忆)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 每次 run 记录用例结果序列,滑动窗口判定
class FlakyDetector:
WINDOW = 20 # 近 20 次结果

def detect(self, case_id, history):
"""
判定规则(满足任一):
A. 近 20 次中 红绿交替 ≥ 3 次(稳定产品代码下结果不稳定)
B. 重跑通过:首次失败、自动重跑 1 次后通过(重跑通过率 0<p<1)
C. 同一断言失败,错误信息在 2 种以上模式间跳变
"""
results = history[-self.WINDOW:]
flips = sum(1 for a, b in zip(results, results[1:]) if a != b)
if flips >= 3:
return FlakySuspect(case_id, "alternating", flips)
...

2. flaky 率看板(团队级 + 用例级)

1
2
3
4
5
6
团队 flaky 率 = 近 7 天被判定 flaky 的用例数 / 总用例数
健康线:< 2%(超过 = 流水线公信力告警)

用例级 flaky 分(0-100):
= 100 × 近 50 次的不稳定度(红绿交替次数 / 49)
用途:排序治理队列(分最高的先修)

flaky 率是"测试系统健康度"的北极星指标——它比通过率更能反映"测试结果可信不可信"。

三、隔离区:显式承认不稳定,限期治理

flaky 用例不能"带着红继续跑"(污染门禁),也不能"直接删"(假装没发生)。隔离区机制:

1
2
3
4
5
6
7
8
9
10
11
12
进入隔离区(自动):
flaky 分 ≥ 60 → 用例打 quarantine 标记
隔离区行为:
✅ 仍每个 build 跑(保持观察)
✅ 结果单独统计(不计入门禁、不算失败)
✅ 报告中"隔离区"独立栏目(可见,不隐藏)
退出隔离区(二选一,必须人工确认):
A. 修稳:连续 30 次全绿 + 根因说明 → 恢复门禁
B. 移除:确认用例无价值/场景已消失 → 归档(留痕,可恢复)
超时升级:
隔离 > 14 天未处理 → @ 用例 owner + 测试 leader
隔离区占比 > 5% → 团队级告警(开始"修系统"而不是"修用例")

隔离区的本质是"公开的不稳定清单"——它把"这个用例爱挂"从口口相传变成带 owner、带期限、带数据的工单。

四、根因分类与典型解法(按占比排序)

治理要按根因下对药。我们半年的数据(214 个 flaky 用例的根因分布):

根因 占比 典型解法
时序/等待不足 38% 固定 sleep → 条件等待;"动画结束"类加视图静止等待(iOS 见 WDA 篇);并发场景加 barrier 同步
数据耦合 27% 用例间共享数据 → 命名空间隔离(数据工厂那篇);全局列表断言"精确数量" → 断言"包含";环境残留 → teardown 强制清理 + 租约回收
环境抖动 15% 网络/设备/时钟 → 环境指纹对账 + 设备健康巡检(degraded 降权);共享 staging 被人工操作 → 测试专用环境
依赖服务状态 10% 依赖的数据状态 → Mock 化(Playwright route / Mock WS);依赖限流 → 测试账号走白名单
真实竞态 bug 8% 修产品代码,不是修用例——这类"flaky"恰恰是自动化的高价值产出
断言过严 2% 断言"精确文案"(含动态数字/时间)→ 断言结构不断言字面值

两个反直觉的结论

  1. "加 sleep"是 flaky 的常见"解法",也是最大根因——sleep 治标(掩盖时序)且制造新 flaky(sleep 不够还是挂)。CI lint 规则:time.sleep 在测试代码里直接告警,强制走条件等待;
  2. 8% 的"flaky"是真 bug——治理流程的副产品是抓到竞态缺陷,flaky 治理做得越认真,产品越干净

五、从"修用例"到"修系统":隔离区超 5% 时

单个 flaky 修用例,系统性 flaky 修系统。隔离区占比持续 > 5% 说明基础设施有问题,按这个顺序排查:

1
2
3
4
1. 数据层:隔离是否彻底?(命名空间/租约/teardown 三件套)
2. 环境层:指纹对账是否每天跑?设备 degraded 是否降权?
3. 时序层:等待策略是否统一?(有没有人还在 sleep)
4. 架构层:被测系统本身有无竞态?(找产品一起看)

修系统的收益是指数级的——数据隔离补全后,我们 27% 的数据耦合类 flaky 一次性清零,而不是修 58 个用例。

六、小结

Flaky 治理五步闭环:定义(区分噪音和竞态 bug)、量化(flaky 率 + 用例 flaky 分)、隔离(公开清单 + owner + 期限)、根因(六类,时序和数据占 2/3)、修系统(占比超 5% 修基建不修用例)。终极标准只有一个:团队看到红的时候,第一反应是"出事了"而不是"又挂了"——这个反应恢复之日,才是 flaky 真正治理完成之时。