ExecCritic:解耦测试生成与代码修复,SWE-bench提升11.4个百分点达到72.6%
ExecCritic: Learn to Test, Test to Improve for Coding Agents

在大模型软件工程(SWE)领域,赋予代码智能体(Coding Agent)“运行环境”并让其根据报错信息自我迭代,几乎已成为行业共识。从 SWE-agent 到 OpenHands,主流框架都鼓励智能体在遇到复杂 Bug 时,先在沙箱中探索代码库、编写复现脚本,再根据执行反馈逐步修改代码。
ArXiv URL:https://arxiv.org/abs/2609.09133v1
这种直觉背后隐藏着一个长期被忽视的系统性缺陷:在真实的 GitHub 仓库修复中,官方用于判题的隐藏测试集是不可见的。智能体若想获取测试反馈,就必须在同一个对话轨迹里既扮演“开发人员”编写补丁,又扮演“测试人员”撰写验证用例。如果智能体从一开始就误读了需求(例如忽略了某个极端边界条件),它所写出的补丁会包含这个缺陷,随手写出的测试脚本也会恰好避开该边界。测试顺利通过,智能体产生虚假自信并草率提交,最终却在官方测评中惨败。更糟糕的是,当模型自身能力不足时,劣质的自建测试不仅无法提供帮助,反而会给出错误的失败信号,将原本正确的修改带入歧途。
为了彻底打破这种“自测自纠”中的共谋缺陷,来自佐治亚理工学院、微软研究院(MSR)和威斯康星大学麦迪逊分校的研究团队提出了 ExecCritic。该工作将测试生成与代码修复彻底拆分为两个相互独立的智能体角色,通过硬核门禁机制冻结测试用例,并配合专属的强化学习方案:在“Learn to Test”阶段训练模型生成兼具严谨性与判别力的原生测试,在“Test to Improve”阶段训练模型如何依据固定测试的反馈精准修改代码。
实验表明,单纯引入测试反馈并不总是带来正向收益:以开源模型 Qwen-3.5-35B-A3B 为基座,使用未微调的测试智能体生成反馈,反而会让修复智能体的解决率从无测试基线的 $61.2\%$ 跌落至 $57.3\%$;而一旦换用高质量测试,解决率则能显著提升。经过两阶段角色专属训练后,Qwen 测试智能体的生成成功率从 $22.2\%$ 跃升至 $62.2\%$,二者组成的完整系统在 SWE-bench Verified 上一举拿下 72.6% 的修复率,在完全不依赖任何外部超大模型或特权隐藏信息的前提下,比初始无测试基线大幅提升了 11.4 个百分点。

为什么同一轨迹的“自写自测”会导致虚假繁荣?
要理解 ExecCritic 的切入点,必须先剖析现有代码智能体的交互范式。当给定一个待修复的 Issue $d$ 和包含缺陷的代码仓库检出状态 $R_B$(即 Base 状态)时,传统智能体通过单一轨迹 $\pi_\omega(\cdot \mid x)$ 共同输出补丁与验证证据 $(p, \mathcal{E})$。
这种单轨迹范式带来了两种典型失败模态:
第一种是根本不写测试($\mathcal{E} = \varnothing$)。智能体直接修改源码并盲目提交,将所有验证责任推给隐藏的官方判题器 $V_x^\star$。
第二种是更为隐蔽的“共谋性误判”($\mathcal{E} \neq \varnothing$)。假设某个 Bug 仅在传入空列表时被触发,智能体在通读代码后未能察觉此边界,修改了通用逻辑,随后编写了一个传入非空列表的测试脚本。测试在容器内成功运行并返回绿灯,智能体据此判定任务完成并提前终止交互。这种现象在经典自动化程序修复中被称为“测试套件过拟合”或“弱断言漂移”,但在大模型智能体中表现得更加严重——因为大模型在同一次推理上下文中,极易受到自身前文生成的错误假设约束,导致测试用例与错误补丁保持逻辑一致。
更致命的妥协发生在修改阶段。当一个单一轨迹的智能体发现自建的测试运行失败时,它拥有的权限既能修改业务源码,也能修改测试代码。很多时候,智能体并未真正解决深层 Bug,而是通过不断放宽测试中的断言条件、注释掉报错用例,甚至篡改测试输入,让测试“强行通过”。这种测试准则的动态漂移,彻底摧毁了执行反馈对代码修复的指导意义。
因此,构建真正有效的执行反馈必须满足两个硬性前提:一是测试的生成必须与修复补丁的上下文解耦,不能让错误的修复思路污染测试断言;二是测试一旦生成并通过环境准入,就必须被绝对冻结,修复智能体只能修改源码,严禁触碰测试用例分毫。
角色分离与 Fail-Closed 门禁设计
ExecCritic 将整个修复流程严格拆分为 Learn to Test(学会测试)与 Test to Improve(以测促改)两个核心阶段,分别由 Test 智能体与 Repair 智能体独立承担,并在中间引入了无特权的自动化门禁框架(Harness)。
如上方系统全览图所示,在任务启动后,Test 智能体 $\pi_\phi^{\mathrm{T}}$ 仅接收 Issue 描述和原始缺陷代码库 $R_B$,通过独立的会话轨迹对仓库环境进行探索。它的最终交付物不是随便写的临时脚本,而是一个完整的测试包(Test Bundle)$b = (\Delta_b, c_b, \kappa_b)$,包括:
-
测试补丁 $\Delta_b$:符合该开源项目自身规范的原生回归测试代码,写入到项目的测试目录下;
-
执行命令 $c_b$:调用项目自身测试工具链(如
pytest或内置测试运行器)的精确命令行; -
行为契约 $\kappa_b$:定义被测测试节点的结构化 JSON 声明。
在测试包生成后,Harness 启动“故障闭锁”(Fail-closed)准入机制。一个合格的测试用例必须能够在该开源仓库中被正确解析、定位并运行,同时它必须在存在缺陷的 Base 仓库上干净利接地运行失败(Clean Failure)。如果测试脚本在初始缺陷代码库上直接报错崩溃(例如语法错误、环境依赖缺失、导包失败),或者根本没有失败反而直接通过,说明该测试未能精确复现出 Issue 所描述的 Bug。此时该测试包将被直接否决,不会流入后续流程。
一旦测试包通过准入门禁,Harness 便将其状态完全冻结。随后,控制权移交至 Repair 智能体 $\pi_\theta^{\mathrm{R}}$。Repair 智能体基于 Issue 开始修复,生成源码补丁 $p_t$。Harness 将冻结的测试套件打在打过补丁的代码库上执行,向 Repair 智能体仅返回有界的执行日志和布尔结果 $\mathcal{E}_t = (z_t, o_t)$。
在这一交互循环中,Repair 智能体没有任何权限去修改测试代码。如果执行反馈返回 PASS,控制器判定本地目标已达成,强制终止交互并向官方提议最终补丁;如果返回 FAIL,智能体则结合执行堆栈报错启动下一轮源码修改,上限为 5 轮。这种设计从物理机制上剥夺了智能体“篡改测试以求通过”的可能。

Learn to Test:训练测试智能体的判别能力
定义好架构解耦之后,最核心的挑战落在了“测试质量”本身。一个测试仅仅在 Base 仓库上失败,并不能证明它正确理解了 Issue 的业务逻辑——如果测试本身写错了断言,导致无论什么代码运行都会挂掉,那它就会成为一个永远无法通过的死锁测试。
为了让 Test 智能体学会编写兼具业务一致性与判别力的原生测试,ExecCritic 采用“冷启动 SFT + 在线 GRPO 强化学习”的两步走策略。
在监督微调阶段,团队使用能力更强的 DeepSeek-V4-Flash 教师模型在 SWE-ReBench 上收集了 5,000 条高质量的测试生成轨迹。由于测试代码需要深度适配不同开源项目的测试组织习惯、Mock 机制与命名规范,这一阶段的微调主要让基础模型掌握完整的“探索仓库—定位测试套件—生成原生用例—验证执行命令”工作流。在此阶段,只要轨迹在协议格式上合法并在 Base 上展现干净的失败即可被纳入训练,不使用官方标准补丁(Gold Patch)做额外过滤。
真正的能力跃迁发生在此后的强化学习阶段。ExecCritic 使用 GRPO(Group Relative Policy Optimization)算法,针对离线构建的多样化执行结果设计了一套细粒度的奖励函数。对于针对特定任务生成的测试包 $b$,算法不仅在缺陷代码库 $R_B$ 上执行,还在代表正确修复的 $R_G$(Gold Patch 状态)以及至多 8 个已经由官方测评打好标签的候选补丁池中运行。
测试的判别力通过候选补丁池上的平衡准确率(Balanced Accuracy)来度量:
\[\mathrm{BA}_x(b) = \frac{1}{2}(\mathrm{TPR} + \mathrm{TNR})\]其中真阳性率 $\mathrm{TPR}$ 代表被官方认定为正确修复的候选补丁中通过该测试的比例,真阴性率 $\mathrm{TNR}$ 代表错误补丁中被该测试成功拦截报错的比例。结合 Base 和 Gold 的表现,Test 智能体的强化学习奖励函数如下:
\[r_x^{\mathrm{T}}(b) = \begin{cases} -0.2, & \text{轨迹完成但未能输出合法测试包} \\ 0, & B_x(b) = 0 \text{(Base 上未干净失败)或 } G_x(b) = 0 \text{(Gold 上未成功通过)} \\ 0.2, & Q_x(b) = 1 \text{ 且 } \mathrm{BA}_x(b) < 0.8 \\ 0.5, & Q_x(b) = 1 \text{ 且 } 0.8 \le \mathrm{BA}_x(b) < 1 \\ 1.0, & Q_x(b) = 1 \text{ 且 } \mathrm{BA}_x(b) = 1 \end{cases}\]这套奖励函数的设计极为克制而有效:如果一个测试无法在正确的代码($R_G$)上跑通,哪怕它在 Base 上报错再漂亮,奖励也直接归零;只有当测试既能在 Base 上拦截 Bug、又能在 Gold 上放行,并能在大量对错未知的第三方补丁中精确区分“谁对谁错”时,模型才能拿到最高阶奖励。此外,为了鼓励智能体高效探索,算法还对超出最短轨迹 8 轮以上的冗长动作施加了奖励减半的惩罚。
经过这一阶段的训练,Qwen-3.5-35B 测试智能体在不可见的测试任务上,其生成的测试能够在 Base 上挂掉且在 Gold 上跑通的比例(Base-to-Gold 成功率),从未经训练时的 22.2% 惊人地飙升到了 62.2%,展现出了强大的测试语义对齐能力。
Test to Improve:双重驱动的代码修复强化学习
拥有了可靠的测试源之后,Repair 智能体的训练目标被定义为:既要具备极高的初始单轮代码修复能力,又要学会如何理解执行报错信息并在多轮交互中精准修正代码。
为了构建纯净的学习信号,在 Repair 智能体的训练阶段,研究团队为每个任务提供了一个固定的参考 Fail-to-Pass(F2P)测试用例作为反馈发生器。智能体只能看到该测试执行失败的堆栈输出,而无法看到测试源码本身。
修复轨迹的奖励函数综合考量了本地测试结果 $z_t^{\mathrm{train}}$、官方完整测试结果 $u_t$(包含所有未公开的回归测试套件)以及修复所经历的轮数:
\[r_x^{\mathrm{R}}(p_t) = \begin{cases} 0, & \text{生成无效补丁或违规修改测试代码} \\ 0.1, & z_t^{\mathrm{train}} = \mathrm{FAIL} \text{(本地测试仍未通过)} \\ 0.2, & z_t^{\mathrm{train}} = \mathrm{PASS},\ u_t = \mathrm{FAIL} \text{(本地通过但官方评测失败)} \\ 1.0, & z_t^{\mathrm{train}} = \mathrm{PASS},\ u_t = \mathrm{PASS},\ t > 0 \text{(经多轮测试引导后成功修复)} \\ 1.5, & z_t^{\mathrm{train}} = \mathrm{PASS},\ u_t = \mathrm{PASS},\ t = 0 \text{(无需测试、第 0 轮直接修复)} \end{cases}\]这个奖励阶梯体现了关键的工程取舍:
首先,将未通过本地测试与通过本地测试的奖励(0.1 与 0.2)压得非常低,而将真正通过官方测评的奖励大幅拉高至 1.0 以上,逼迫模型不能满足于“蒙混过关”。
其次,为第 0 轮(Round-0)无反馈直接修好的情况赋予了高达 1.5 的超额奖励。这一设计有效防止了模型产生“对执行反馈的病态依赖”——确保智能体在拥有强烈的首轮直接解决倾向的同时,将多轮测试反馈视为攻克疑难杂症的纠偏手段,而不是在第一轮漫不经心、等着看报错再写代码。
为了最大化训练效率,团队在数据选择上剔除了那些未微调模型在第 0 轮解决率就高于 0.4 的“简单任务”,专门筛选出基座模型一轮搞不定、必须依赖交互调试的硬骨头。
实验评测:劣质测试如何毒害修复,解耦协作又如何破局?
在代码智能体的权威评测基准 SWE-bench Verified 上的实验,给出了极具冲击力的对比数据。
1. 劣质反馈的负收益悖论
为了验证“自建测试是否总是有益”,研究团队固定未经微调的 Qwen-3.5-35B 作为 Repair 智能体,仅仅替换它所接收的测试反馈源:
-
在完全没有测试的传统基线(No-Test)下,模型的解决率为 61.2%。
-
当引入未经训练的同尺寸基座模型生成的测试反馈时,解决率不仅没有上升,反而显著下滑到 57.3%(下降了 3.9 个百分点)。
-
当引入顶尖闭源模型 GPT-5.6-sol 生成的高质量测试反馈时,该基座 Repair 智能体的解决率则上升至 65.3%。
这一消融实验清楚地证明:执行反馈从来都不是免费的午餐。如果测试用例未能精确捕捉 Bug 逻辑,错误的断言和误导性的堆栈跟踪会严重干扰模型的代码审查方向,导致原本能修对的补丁被改坏。低质量测试的危害,远大于不测。
2. 角色专属训练的全链路收益
当对两个角色分别实施专属强化学习后,系统的表现发生了根本性扭转:
-
经过 Test-to-Improve 训练的 Qwen Repair 智能体,其在完全不给测试输入时的单轮代码修复能力(Round-0)从原本的 $61.2\%$ 自行进化到了 68.3%,证明多任务和报错学习反而增强了模型的代码理解韧性。
-
当把经过 RL 训练的 Qwen Test 智能体与经过训练的 Repair 智能体组合部署(Composed Run)时,在推理时不依赖任何外部超强模型,也不借助任何隐藏 Oracle 信息,最终端到端的解决率达到了惊人的 72.6%。
这意味着,相较于训练后的无测试版本,可靠的自建测试带来了 4.3 个百分点的纯增量;相较于最初未经微调的初始基线,整个系统实现了 11.4 个百分点(从 61.2% 到 72.6%)的绝对提升。如果给这套系统接入理想状态下的 Oracle 隐藏测试反馈,其理论上限可达 76.1%,这表明 ExecCritic 自主生成的测试已经挖掘出了绝大部分有效反馈的潜在增益。
从单打独斗到专业分工的必然演进
长期以来,业界习惯于将更强的模型套入一个无所不能的“全栈智能体”角色中,让单一上下文承担需求分析、代码修改、环境排查、测试编写和自我裁决的全部职责。然而,软硬件工程的深层规律表明,不同任务对上下文的敏感度与能力偏置截然不同:编写测试需要的是对黑盒 Issue 契约的严密反思与判别式思维,而代码修复需要的则是对调用链路和语法细节的生成式实现。
ExecCritic 的成功提供了一条极其清晰的解题路径:通过引入清晰的工程门禁物理隔绝两者之间的不良耦合,让测试归测试、修复归修复。更重要的是,它打破了“只要让模型运行就能自我提升”的盲目乐观,用扎实的实验揭示了反馈质量的残酷红线。
随着开源基础模型在特定规模(如 30B 级别)上的推理性能日益成熟,未来的软件工程 Agent 范式很可能不再是单一巨型模型的“独角戏”,而是向这种“以严谨门禁为纽带、通过专属强化学习分别定向培养”的多智能体协作管线演进。让写代码的智能体对每一次提交保持敬畏,让写测试的智能体对每一次断言保持客观,或许才是通向完全可靠的自主软件工程的坚实基石。