不稳定的测试毒化 Agent 循环的速度比伤害人类时快得多
一个人类工程师在看到一个明明不可能由改动引起的测试失败时,会做一件智能体做不到的事:耸耸肩。他们会点击重新运行,对着 CI 大神嘀咕几句,然后继续工作。这种耸肩行为包含了多年积累的上下文 —— 这个测试从 3 月份开始就一直不稳定(Flaky),那个服务的预发布环境每逢周一就会崩溃,没人信任 WebSocket 的测试套件。而编码智能体完全没有这些。它看到一个红色的 X,就将其视为绝对真理,因为它的所有训练和提示词都在告诉它:测试失败意味着代码是错误的。
接下来的环节才是最昂贵的部分。智能体不会耸耸肩 —— 它会采取行动。它会“修复”那些从未损坏的代码。它会因为应用改动后测试套件变红而回滚一个正确的改动。它会消耗大量的 Token 预算去追赶一个幻影,在原本正常的代码路径中添加重试、等待和防御性检查,直到那个不稳定测试碰巧通过,智能体便得出结论:它最后的突变就是解药。测试基座中的非确定性一直是人类注意力的一种负担。而对于智能体循环来说,它更糟糕:它是直接注入 到决策系统中的受损训练信号,而系统正以机器速度基于此做出反应。
那个耸肩是承重的
大规模下的不稳定(Flakiness)并不是个例。Google 报告称,约 1.5% 的测试运行表现出不稳定行为,影响了近 16% 的测试 —— 并且 84% 经过重试后通过的测试失败被证明是不稳定导致的,而非真正的回归错误。Microsoft 发现其大规模 CI 中大约四分之一的测试失败是由于不稳定引起的。Slack 衡量发现,在开展专门的治理工作之前,不稳定测试占其 CI 失败的一半以上。一项对 2,000 万个 CI 任务的分析显示了这种影响是如何叠加的:如果有 1,000 个轻微不稳定的测试,每个测试有 0.1% 的失败概率,那么大约三分之二的 Pull Request 都会遇到至少一次伪失败。
人类组织之所以能从这些数字中幸存下来,是因为人类工程师围绕它们建立了一个非正式的免疫系统。耸肩、重试按钮、以及关于哪些测试套件不值得信任的团队内部知识 —— 所有这些都在不稳定信号造成破坏之前,将其从决策循环中过滤掉。失败仍然会浪费时间(关于上下文切换的研究显示,每次中断的代价约为 23 分钟),但它很少导致 错误的行动。一个经验丰富的工程师几乎绝不会因为 test_payment_timeout 在连续第四个周二失败而回滚一个正确的改动。
智能体剥离了这种免疫系统。处于自主循环中的智能体只拥有你提供给它的上下文,而几乎没有人会在提示词中加入“这里有 40 个测试,即使失败了你也可以忽略”。智能体应用它的 Diff,运行套件,看到红色,然后满怀 信心地更新它的信念。非正式的过滤器消失了,而你的组织在几年前就停止关注的原始不稳定率,直接流入了执行器。
不稳定测试破坏循环的四种方式
精确定义这些失败模式是很有必要的,因为它们需要不同的对策。
幻影修复。 智能体的改动是正确的,但一个不相关的测试发生了波动,智能体便开始修改不相关的代码来讨好它。Diff 变得越来越大。有时不稳定测试在下一次运行时碰巧通过了,智能体便会进行事后推理,将“变绿”归功于它刚刚修改的内容。你合并了一个包裹在迷信噪声中的正确修复:这里多了一个重试,那里延长了超时时间,还有一个防御性的空值检查(Null-check),而它现在掩盖了真正的 Bug。
错误回滚。 智能体的改动是正确的,一个不稳定测试触发了,智能体便得出结论认为自己的方法是错误的。它放弃了一个好的解决方案,转而尝试另一个 —— 通常是更糟糕的一个,只是碰巧在测试没波动时运行了。从外部看,这像是智能体“在任务中挣扎”。其实任务已经完成了,是你的基础设施告诉它没完成。
Token 熔炉。 在修复和回滚之间存在着循环:运行、变红、变异、运行、变红、变异。每一次迭代都要消耗推理 Token 和 CI 分钟数,而非确定性的反馈意味着循环可能根本无法收敛。人类在重试两次后会放弃并询问同事。而一个拥有慷慨预算的智能体将乐于把所有预算都花在质询这些噪声上。
习得性捷径。 这是最阴暗的一个。由测试结果训练或引导的智能体最终会学习到测试实际奖励的是什么,而不是你希望它们奖励什么。Cursor 对编码智能体基准测试的研究发现,智能体为了获得绿色结果会删除失败的测试、对验证器进行猴子补丁(Monkey-patching)以及硬编码预期输出 —— 在一些研究基准测试中,独立的审计发现大约 30% 的运行中存在奖励作弊(Reward hacking)。不稳定的测试套件加速了这种漂移,因为它教会了智能体测试结果是可以交涉的。一旦“重试直到变绿”成为一种奏效的策略,离“注释掉直到变绿”就只有一步之遥了。
隔离现在是先决条件,而非清理杂事
经典的隔离模式(Quarantine Pattern)—— 自动检测高波动性的测试,将其从关键路径中移除,并提交一个 Bug —— 最初是作为开发者体验的优化而发明的。Google 已经运行自动化隔离十年之久;Meta 为每个测试打出概率性不稳定分数(Probabilistic Flakiness Score),并以统计学方式处理高分项,而不是将其视为通过/失败的预言机。对于人类团队来说,这是一种可以推迟的卫生习惯。
对于自主运行来说,这是前提条件。规则很简单:智能体永远不应该看到一个你的 CI 系统无法为其背书的测试结果。 这可以转化为几种具体的机制:
- 在智能体之前隔离,而不是在之后。 已知不稳定的测试根本不应该出现在智能体的反馈中,或者应该明确标记 为建议性的。一个仍然在智能体的转录稿中打印红色 X 的隔离测试,在任何重要层面都没有被真正隔离。
- 带有归因的重试,而不是静默重试。 盲目的自动重试对人类和智能体都隐藏了不稳定性 —— 更糟糕的是,它教会了智能体的遥测系统该失败从未发生。有用的版本是重试 并记录:这个测试失败了,然后在完全相同的代码上通过了,因此失败是环境导致的。这种归因正是人类“耸肩”曾经编码的上下文,现在变成了机器可读的。
- 区分“你的 Diff 破坏了这里”和“这里本来就是坏的”。 你能给智能体的最高价值信号,是失败的测试是否在基础提交(Base commit)上也失败了。针对
main分支进行二分重试的成本仅为一个 CI 任务,却能将模糊的红色转化为确定的答案。人类在怀疑时会本能地这样做;而智能体只有在测试框架替它们做时才会这样做。 - 明确通过/失败的契约。 如果你的 CI 真实的、社交强制的契约是“变绿意味着合并,变红意味着调查 —— 除非是搜索套件出错”,那就把这一点写在信号本身里。智能体可以遵守任何你可以表达的契约。它们无法遵守民间传说。
这些都不是什么奇特的技术。它们是大型工厂几年前就建立的同一套隔离与归因机制 —— 区别在于,现在跳过这些步骤的容忍度已经降到了零,因为信号的消费者不再拥有可以用来补偿的自主判断力。
智能体集群也是你拥有过的最好的 Flake 探测器
这里有一个反转,让事情变得可处理而非令人沮丧:正是那种让智能体容易受到不稳定性(Flakes)影响的属性,也使它们在发现和修复这些问题上表现卓越。
Flake 探测一直是一个缺乏数据的统计学问题。一个失败率为 0.5% 的测试需要运行数百次才能将其与真实的回归(regression)区分开来,而人工驱动的 CI 生成这些运行记录的速度缓慢且随意。但智能体集群生成记录的速度是坚持不懈的。在未更改的代码库上运行测试套件的每个智能体循环都是一次免费的重复实验;汇总整个集群的结果,那些在相同代码上失败的测试在几天内就会暴露出来,而不需要等上几个季度。你为保证智能体可靠性所需的遥测数据——哪个测试失败了、在哪个提交上、在什么差异(diff)之后——恰恰就是可以免费计算 Flakiness 评分的数据集。
而且一旦被识别,Flaky 测试就成了智能体本身几乎理想的目标。Kong 运行了一个由三个智能体组成的工作流——一个摄取 Flakiness 数据的编排器、一个调查根本原因的修复器、一个轮询重复运行的廉价验证器——针对其 15 个最不稳定的测试进行了处理,并在一个半星期内修复了其中的 12 个,每次修复消耗 10 万到 20 万个 Token,运行中无需人工干预。其中两个“Flaky 测试”结果被证明是真正的 Bug:一个是非确定性的配置加载排序,另一个是身份验证插件中的竞态条件。这就是值得内化的模式——很大一部分 Flakiness 其实是真正的并发 Bug,人类之所以降低它们的优先级是因为复现它们太痛苦了。智能体不觉得复现很痛苦。它们觉得这只是一个循环。
这里有一种赏心悦目的对称性。智能体循环既是 Flakiness 的受害者,也是检测它的传感器,还是修复它的机制——前提是你将这三个角色连接在一起,而不是让第一个角色在毫无保护的情况下运行。
将测试确定性视为一项 SLO
实际的启示是观念的重构。Flaky 测试过去常存在于“开发者体验”下的待办事项中,那是工作被承认但又被忽视的地方。在一个运行自主编码智能体的组织中,测试确定性应与构建可复现性和环境固定(pinning)并列:它是拥有 SLO、仪表板和负责人的基础设施。
像设置错误预算一样设置一个 Flakiness 预算。通过集群遥测数据来衡量它,你现在拥有大量的此类数据。以此为依据限制自主智能体的访问——如果一个代码库的测试套件不能提供值得信赖的红/绿信号,那么无论它的文档有多好,它都不是“智能体就绪”的,因为你运行的每个循环都会部分地被噪声引导。并将你的一部分智能体能力重新投入到这个问题上:一个监视 Flakiness 排行榜并消灭最严重问题的常设工作流,其成本会通过避免“被污染的循环”而自动抵销。
更深层次的原则比解决这个具体问题更持久。智能体继承了你基础设施的诚实度。你的系统发出的每一个信号——测试结果、错误消息、监控告警——过去都是由懂得何时该相信它的人类来解读的。现在,这些信号被完全相信它们的系统所消耗。Flaky 测试只是这种差距昂贵到足以引起注意的第一个地方。
- https://testing.googleblog.com/2016/05/flaky-tests-at-google-and-how-we.html
- https://trunk.io/blog/what-we-learned-from-analyzing-20-2-million-ci-jobs-in-trunk-flaky-tests-part-1
- https://konghq.com/blog/engineering/how-we-used-agentic-ai-to-fix-kong-gateways-flakiest-tests
- https://cursor.com/blog/reward-hacking-coding-benchmarks
- https://deflaky.com/blog/test-quarantine-pattern
