Agent 没挂,是你的测试挂了

今天读到 GitHub 博客上一篇讲 agent 测试的文章,看得我一愣一愣的——因为它讲的,正是我最近踩的坑。
上个月我在写一个自动化流程,让 Copilot Agent 帮我跑一套 CI。周二一切正常,绿得发光。周三同样的代码,红了。我 debug 了一下午,最后发现:agent 没做错任何事。只是因为网络慢了点,loading 画面多停了两秒,agent 等了一下然后继续——任务最终完成了。但测试脚本看到的是”等等,你怎么在这个状态多停了 2 秒?预期不是这样的。挂。”
agent 没挂。是测试挂了。
这不是我一个人的倒霉事。GitHub 的 Gaurav Mittal 和 Reshabh Kumar Sharma 写了篇长文讲这个问题——“当’正确’不是确定性的时候,怎么验证 agent 行为”。读完我想把最核心的东西讲给你听,顺便聊聊我自己的理解。
为什么传统测试在 agent 面前全跪
Section titled “为什么传统测试在 agent 面前全跪”四种传统测试范式,放到 agent 场景里各有各的死法:
断言测试:你得手动写每个检查点。Agent 能用快捷键打开搜索,也能从菜单进去——两条路都正确。但你的断言写了”必须出现菜单点击事件”。挂。
录制回放:敏感到发指。截图里多了一个 “加载中” 的 spinner?挂了。窗口位置偏了 3 个像素?也挂了。这些都跟”任务完没完成”毫无关系。
视觉回归:逐像素比较两张截图,但没有语义理解。它不知道时间戳变了不算 bug,也不知道某个按钮消失了才是真 bug。
ML oracle:黑箱判断,要几千个训练样本,判断完了还不告诉你为什么。测试挂了,你盯着日志:所以呢?哪一步出了问题?
四种工具,同一种病灶:它们都假设”正确=按规定的序列走”。对于确定性代码,这个假设将就用。对于 agent——agent 天然不确定,多条路径可以到达同一个正确终点——这个假设就崩了。

必经之路 vs 偶发噪声
Section titled “必经之路 vs 偶发噪声”文章把 agent 执行过程中的状态分成三种,干净利落:
必经状态(essential states):没它任务就成功不了。比如”搜索结果页面”——你做搜索,没到搜索结果页,就是没成功,没商量。
可选变化(optional variations):环境导致的偶发状态。loading spinner、桌面背景变化、窗口标题栏的细微差异——出现了不代表错,没出现也不代表对。
汇聚路径(convergent paths):不同的操作序列最终汇到同一个状态。Ctrl+Shift+F 打开搜索框,还是从菜单点进去——两条路,抵达同一个地方。
我读到这里想到一个比喻:从北京到上海,坐高铁、飞机、自驾,三条路完全不同。传统的断言测试就像要求你必须走京沪高速的第 37 号出口——你没走那个出口,哪怕你已经在上海了,它也判你失败。
换个说法:确定性软件的测试在问「你走了这条路吗?」agent 测试该问的是——「你到了吗?该经过的地方经过了吗?」

把编译器理论搬到 agent 测试
Section titled “把编译器理论搬到 agent 测试”文章最精彩的部分来了——怎么自动判断哪些状态是”必经”的?答案来自编译器理论里的一个老概念:支配者分析(dominator analysis)。
在控制流图里,节点 A “支配”节点 B,当且仅当每条从起点到 B 的路径都必须经过 A。听起来学术,翻译成人话就简单了:你把 agent 的几次成功执行画成图——状态是节点,动作是边——然后算每个节点的支配关系。在所有成功执行中都出现的节点,且每次都出现在关键位置,就是”必经节点”。有时候出现有时候不出现的(loading spinner),就是”可选噪声”。
这是数学上的必然,不是人工判断。不需要写 spec,不需要标注数据。
但有个前提:你得先把执行记录建模成图而不是线性脚本。文章里用的结构叫 Prefix Tree Acceptor(PTA)——把多次执行轨迹合并成一棵有分支、有汇聚的图。能分支的地方自然分支(loading screen 出现 vs 不出现),能合并的地方合并(两条路径最终都到了”搜索结果”)。这给了”多条路径都正确”一个数学表达。
本质上它在做一件事:从「步骤是否正确」转向「结构是否完整」。

最难的问题:两张截图是不是同一个状态
Section titled “最难的问题:两张截图是不是同一个状态”合并图的时候有一个棘手问题——怎么判断两次执行中的两个截图代表的是同一个状态?桌面截图里时钟从 10:23 变成 10:24 了——这是同一个状态还是不同状态?
文章给了一个三级方案:
第一级,感知哈希和 SSIM。几乎相同的直接合并,毫秒级。
第二级,多模态 LLM 语义判断。视觉指标模糊时让 LLM 看:「这两个截图是同一个 UI 状态吗?」LLM 知道忽略时间戳变化和窗口装饰差异,但会标出”这个按钮消失了”。
第三级,保守合并。不确定就不合并,宁可保留分支。
LLM 在这里的角色被克制地使用——不是让它判断整个任务对不对,只在等价性判断模糊时出场,且规则明确。避免了”用黑箱判断黑箱”的套娃陷阱。这思路很务实:AI 不替你拍板,替你看图。
数字不会骗人
Section titled “数字不会骗人”他们在 VS Code agent 测试套件上做了对比实验。一边是 agent 自评(CUA 自己报告”我成功了”),一边是支配者树分析:
Agent 自评准确率:82.2% 支配者树准确率:100%
更狠的是”not a bug”识别——当测试失败不是因为代码问题,而是 agent 自己执行出了偏差时,agent 自评完全无法识别(F1 = 0%)。它连自己错在哪都不知道。
这验证了一个我一直不太敢确认的直觉:agent 不能给自己的作业打分。验证必须来自系统外部——不是另一个模型看一眼说”嗯还行”,而是数学上可证明的必经节点是否被经过。

这对日常开发意味着什么
Section titled “这对日常开发意味着什么”Trust Layer 本质上是把测试从”检查步骤”升级成”检查结构”。部署到 CI pipeline 后,它不再因为一个 loading spinner 多转了半秒就把 build 标红。
我觉得最值的:只需要 2-10 次成功执行记录,就能自动构建”正确性的定义”。对回归测试而言这意味巨大——维护自动化测试最痛苦的从来不是写测试,是维护。UI 每改一点点,测试脚本就挂一片。如果验证对象从”特定 UI 操作序列”变成”必经状态的拓扑顺序”,维护成本会断崖式下降。
当然它有限制:需要成功 traces 才能学,不能从失败日志反推正确长什么样。LLM 依赖也会带来延迟。但方向是对的。
验证 agent,不能靠更复杂的脚本来判断另一个脚本。需要结构的、可解释的外部判断层。Trust Layer 真正的含义——信任不是靠 agent 说”相信我”,是靠数学说”它确实经过了必须经过的地方。”
你有没有遇到过”agent 明明做对了,但测试判失败”的情况?当时是改 agent 还是改测试来解决的?
Gaurav Mittal & Reshabh Kumar Sharma. Validating agentic behavior when “correct” isn’t deterministic. GitHub Blog, May 6, 2026. https://github.blog/ai-and-ml/generative-ai/validating-agentic-behavior-when-correct-isnt-deterministic/