详解 flaky_rate 的统计定义(分母是 run 而非 attempt),指出「有些 flaky 是可接受的」需要量化才能指导行为,并给出 30 天窗口的计算方法。
关于不稳定测试的大多数争论,本质上是对分母的争论。先把这个定清楚。以下是一个可用的定义,也是本文其余部分使用的定义:
flake_rate(test, window)
= flaky_runs(test, window) / total_runs(test, window)
其中,如果在一个 CI 调用中、同一提交上,测试既失败又通过,
则该次运行被判定为 FLAKY——即同一提交上某次尝试失败,
而同一测试的重试成功。
所有尝试均失败的运行是 FAILURE,不是 flake。
首次尝试即通过的运行是 PASS。
其中有三处选择值得说明。按运行计数而非按尝试计数,意味着一个重试五次的测试不会看起来比只重试一次的测试flakiness高五倍。要求在同一提交上出现两种结果,是为了排除真正的回归——那是另一个问题,归另一拨人管。保持一个窗口——三十天是合理的默认值——意味着统计量会在有人修复问题时响应,而不是被三月那两周的糟糕表现永远拖累。
一个套件如果每个提交只运行一次每个测试,就根本无法计算这个指标,因为在没有第二次尝试的情况下,flake 和 failure 是无法区分的。这就是即使在不需要重试就能保持绿色的套件上也要设一次重试的实际理由:正是重试使得这个统计量变得可测量。
还有一个决定必须明确做出,因为把它留给隐式约定会导致两个人从同一份数据中引用出不同的数字:即在分支上运行的测试是否与在默认分支上运行的测试等量齐观。分支运行的噪声更大——被测代码只完成了一半——把它们算进来会抬高所有比率;排除它们又会大幅缩减样本量。可行的做法是:基于所有运行计算统计量,但仅在默认分支的运行上进行排名和强制执行,并且说明你所引用的数字是哪一个。在实践中,这意味着你要分别报告两套数字。
没有任何公开的数字对你的套件有意义,也不存在什么行业比率可以引用——这个数字取决于你的提供商、你的模型、你的 temperature、你的并发量以及你的断言内容。计算你自己的。如果你存储的是每个尝试一行数据(格式符合最小化 dashboard 使用的形状),那只需要一条查询:
-- one row per (run_id, test_id, attempt, status)
WITH per_run AS (
SELECT run_id,
test_id,
MAX(CASE WHEN status = 'pass' THEN 1 ELSE 0 END) AS any_pass,
MAX(CASE WHEN status = 'fail' THEN 1 ELSE 0 END) AS any_fail
FROM test_attempts
WHERE started_at >= NOW() - INTERVAL '30 days'
GROUP BY run_id, test_id
)
SELECT test_id,
COUNT(*) AS runs,
SUM(any_pass * any_fail) AS flaky_runs,
ROUND(100.0 * SUM(any_pass * any_fail) / COUNT(*), 2) AS flake_pct
FROM per_run
GROUP BY test_id
HAVING COUNT(*) >= 30
ORDER BY flake_pct DESC;
HAVING COUNT(*) >= 30 不是装饰。一个只有四次运行、一次 flaky 的测试,点估计是 25%,但置信区间宽到估计本身毫无信息量;以它做排名会让新测试永远排在待办清单的最前面。小样本问题及其处理方法属于 flakiness score 的范畴。
直觉会告诉你每个测试几个百分点听起来无害。但套件规模会摧毁这种直觉,因为独立的每个测试 flake 率是相乘的。如果一个套件有 n 个测试,每个测试以独立概率 p 发生 flake,那么整个运行是绿色的概率是 (1 - p)^n。
P(green suite) = (1 - p) ** n
n = 200 tests
p = 0.05 -> 0.95 ** 200 = 0.000035 (几乎不可能绿色)
p = 0.02 -> 0.98 ** 200 = 0.0176 (绿色约 1.8% 的运行)
p = 0.005 -> 0.995 ** 200 = 0.3670 (绿色约 37% 的运行)
p = 0.001 -> 0.999 ** 200 = 0.8186 (绿色约 82% 的运行)
n = 40 tests
p = 0.005 -> 0.995 ** 40 = 0.8183
p = 0.002 -> 0.998 ** 40 = 0.9231
独立性同样是假设,而在这里这个假设保守得帮了倒忙:LLM 测试的失败往往是相关的,因为一次提供商事故会同时击中所有测试。相关性使得绿色构建的概率比这个公式预测的要好,但同时让红色日子变得更糟——你会看到漫长的干净区间被一次四十个测试同时失败所打断。这种模式正是为什么在这里按原因对失败进行分组比在普通套件中更重要。
这张表的实践含义是:每个测试的容限不能脱离套件规模独立设定。当套件有四十个测试时写的策略,到了两百个测试时就会悄然失效。将模型调用测试数量翻倍,大致上会将每个测试允许的容限减半。每当测试数量发生实质性变化时就重新计算,并在阈值旁边记录套件规模,这样下一个人就能看到这个假设是在什么条件下推导出来的。
将公式反转。决定什么样的运行应该是不经任何人工干预就保持绿色的比例——记为 G——那么每个测试的预算就是 p = 1 - G^(1/n)。
p = 1 - G ** (1 / n)
G = 0.95 (95% 的运行绿色), n = 200 -> p = 1 - 0.95**0.005 = 0.000256
G = 0.90, n = 200 -> p = 1 - 0.90**0.005 = 0.000527
G = 0.90, n = 40 -> p = 1 - 0.90**0.025 = 0.00263
这些预算比流传的"低于 2%"这个民间数字要紧得多。这才是有用的发现:对于任何规模的套件,每个测试个位数的容限不会产生绿色的 pipeline。它产生的是一个几乎总是红色的 pipeline,以及一个已经停止阅读它的团队。如果你的预算是你对于调用模型的测试根本无法达到的率,结论不是放宽预算——而是你应该有更少的测试在调用模型。把大多数测试切换到录制好的响应上(不调用模型的测试),只保留一小部分 live 集合来做真正的网络调用。
数字本身不如附加在它身上的后果来得有价值。能够改变行为的策略有四个条款,放在贡献指南的一段里就够了:
统计量的定义,如上所述,基于一个具名的窗口、从一个具名的表计算。这里的歧义是争论重新开始的根源。
阈值,源自你自己的套件规模和你自己的绿色构建目标,并记录推导过程,以便下一个人的套件翻倍时能够重新推导。
后果。超过阈值的测试自动被隔离,而不是拿来讨论——策略的意义正在于移除讨论。隔离有其独立的接线方式和退出标准。
review 节奏。有人在固定的时间表上查看排名列表。没有这一点,策略就只是一个没人去评估的阈值。
把推导过程和数字一起发布。附有推导过程的阈值可以在其假设上受到挑战——这是富有成效的争论;而赤裸的阈值只能基于权威来挑战——而这正是制定该策略要终结的争论。在实践中,人们需要的那句话是说明这个数字能买到什么:在这个容限和这个套件规模下,大约这个比例的运行会在无人干预的情况下保持绿色。
不要包含的一个条款:"调用模型的测试可以豁免,因为那些本质上是不稳定的"。它们本质上是 nondeterministic 的,这不是同一回事。一个对 schema、工具名或不变量的断言进行测试的测试,可以像任何其他测试一样稳定;而这个豁免条款会移除本可以推动它达到那种稳定性的压力。