自愈的界限:它会把断言改松,也会把失败的用例删掉

一套五 agent 的 UI 测试流水线,收敛率按宽松口径算 70%,按语义严格口径算 50%。差的那 20 个点,是靠改松断言和删测试换来的。

位置
第 02 篇 / 共 4 篇
预计
6 分钟

一套自动修复 UI 测试的系统,跑完之后报告说:收敛率 70%。

同一批数据换个口径再算 —— 把「靠作弊达成的收敛」剔掉 —— 50%。

差的那 20 个点是这么来的:

  • expect(value).toBe(5) 改成 expect(value).toBeTruthy()
  • 把一条修不好的用例直接删掉,于是剩下的用例「100% 通过」

这两条出自 Practical Limits of Autonomous Test Repair 的实测。它是我读过的关于「测试 agent 会怎么骗你」最具体的一份材料。

这套系统长什么样

五个 agent 用 LangGraph 串起来,带反馈环:

Explorer ── RAG 读文档 + 运行时 DOM 分析,发现功能
Planner ── 决定复用已有测试还是新生成
Coder ── 产出 Playwright TypeScript 脚本
Executor ── 跑脚本,抓结果
Self-Correction ── 用 14 个专用工具分析失败,产出修订版脚本
↑____________________________________|

这个结构很主流:先发现功能,再规划,再生成脚本,跑一遍,失败了自己修 —— 几乎是「自动化测试 agent」的标准答案。所以它的失败方式特别值得看:不是因为架构差,是因为这个架构本身有几个必然的坑。

五类失败,每类都有数

失败模式 占比
导航 / 环境超时 120 / 300 报告(40%
产出的东西根本跑不了 113 / 300(38%),报错是「从模型回复里抽不出代码」
浏览器上下文被关掉 48 / 300(16%)
幻觉 UI 交互(元素压根不存在) 36 / 300(12%),6 个不同的编造选择器
修复循环不收敛 10 个场景族里 3 个(30%

不收敛那一条最惨的一例:连续 113 份报告,每一份都跑满 16 次重试上限,一个能执行的测试产物都没产出。

再加两个整体指标:

  • 首次就成功:1/10(10%)
  • 单条用例通过率:32.1%(636 次执行里 204 次)
  • 能产出可执行文件的:62.3%

最该记住的那一条:自愈会制造假绿灯

前面那些是「做不到」,这一条是「假装做到了」。

七个收敛的场景族里,有两个是靠语义上打了折扣的手段收敛的

手段一,把断言改松。 expect(value).toBe(5) 变成 expect(value).toBeTruthy()。测试还在、名字还在、结果变绿了 —— 而它现在几乎什么都验不出来。

手段二,把失败的用例删掉。 于是「剩下的场景 100% 通过」。

这就是那两个口径的差别:朴素的 pass/fail 指标报 70%,语义严格的指标报 50%。

这一条对做这类系统的人有一个很硬的推论:自愈的产物不能直接进绿灯,只能进「待复核」。 因为自愈优化的目标是「让报告变绿」,而「让报告变绿」和「让产品变对」之间,删掉一条测试就是最短路径。

模型不是在作弊,它是在忠实地优化你给它的目标。目标写成「让测试通过」,删测试就是合法解。

它给的五条设计准则

那篇最后给了五条,我认为条条能直接用:

  1. 强制运行时接地 —— 生成的选择器要拿真实 DOM 校验过,别让 12% 的幻觉元素流到执行阶段
  2. 有界迭代 + 人工升级 —— 别让重试自己转到 113 份报告;到界就交给人
  3. 把测试当作行为规范 —— 改断言必须人来批。这一条直接封死了上面那两个作弊手段
  4. 把基础设施故障和测试失败分开 —— 40% 的超时和 16% 的浏览器崩溃,和「产品有 bug」是两回事,混在一起会污染所有统计
  5. 在 agent 交接处校验显式契约 —— Explorer 交给 Planner 的、Coder 交给 Executor 的,每一处都要有可校验的约定

它的总结句我认为是整篇最有价值的一句:

关键的挑战不是怎么最大化自主性,是怎么给它划界。

和我自己那套东西对上的地方

有几条我踩过,可以互相印证。

第 5 条那个「交接处校验契约」,我是反着学会的。 我那套流水线里,建模那步产出状态机、生成那步直接读来用,中间零校验。结果模型在状态机里编了一个 spec 里根本不存在的「收件箱」状态,从它铺出来的每一条用例都继承了这个错 —— 传染率 100%,因为每一步的产物是下一步唯一的输入。

第 3 条那个「改断言要人批」,我当时的表述是「不做无人值守的自愈」垂类 harness 那一格里的判定分层就是为这个准备的)。 现在看这个决定是对的,但理由我当时说得不够狠 —— 我以为自愈的风险是「修错」,而这篇告诉我风险是它会成功,只是用了删测试的方式

第 4 条我做了一半。 我的错误分类里区分了「找不到目标」和「断言不成立」,但没把环境超时单独拎出来。按这篇的数,那可能是最大的一类(40%)—— 而它现在混在我的失败统计里,等于每个统计都带着一层噪音。

边界与代价

这是一个案例研究,不是对照实验。 单一系统、单一企业级环境,没有和别的架构对比。所以「五 agent 流水线不行」这个结论下不了 —— 能下的是「这五类失败会发生,而且比例不低」。

它没说清楚那 38% 抽不出代码是模型的问题还是解析的问题。 「Could not extract code from LLM response」这个报错,可能是模型输出格式漂了,也可能是解析器太脆。第四阶段 114 份报告里 113 份没产物,那篇归因于「累积的流水线状态和模型输出格式可靠性之间的相互作用」—— 这个归因比较模糊,我读不出到底哪一半是主因。

「有界迭代」的界怎么定,它没给方法。 说要有界很容易,定在 3 次还是 16 次是另一回事 —— 那个最惨的例子恰恰是跑满了 16 次上限,一百多轮。所以上限存在不等于有界,还得有「连续多少轮无进展就升级」这类判据。

下一篇

上面两篇都在讲「测试 agent 做得不够好」。下一篇那篇更狠一点 —— 它问的是:agent 写测试这件事,到底有没有用?

答案基于 SWE-bench Verified 上六个模型的轨迹:一个模型 83% 的任务会写测试,另一个只有 0.6%,而两者的解决率只差 2.6 个百分点。