Coding agent写测试集体失败,AI代码合格率被高估一倍
Coding Agent写代码的能力越来越强,写测试的能力也在同步提升。从为函数补单元测试、为GitHub Issue编写复现用例,到为整个代码仓库构建回归测试,Codex、Claude Code等前沿Coding Agent已经能够浏览仓库、修改实现,并借助执行反馈不断修正测试。 测试的地位也随之变化:它不仅是软件质
如今Coding Agent写代码的本事见长,写测试的水平也在往上走。小到给函数补单元测试、替GitHub Issue写复现用例,大到为整个代码仓库搭回归测试,
Codex、Claude Code这类前沿Coding Agent都已经能浏览仓库、改动实现,再凭借执行反馈一遍遍修测试。
测试的角色也跟着变了。它不再只是软件质量的保障,还常被拿来当Agent自我验证的依据、代码评审的证据,甚至充当强化学习中的奖励信号。测试写得靠不靠谱,直接决定这些下游环节敢不敢信它的判断。
怎样才算一份好的测试?
眼下主流的测试生成Benchmark大多走单一参考评测(Single Reference Evaluation)的路子:生成的测试只要在原始代码上失败、在一份标准答案(Gold Solution)上通过,就算成功。可同一个需求往往有多种正确实现,也有不少看着合理、实则暗藏缺陷的错误实现。
一份测试可能刚好「认得」标准答案,却漏过了真正有Bug的代码;也可能把标准答案的实现细节当成需求本身,把别的完全正确的写法挡在门外。只盯一份参考答案,测试质量很容易被高估。
南京大学刘佳恒老师团队近日提出了新的测试生成Benchmark TestPrism,含来自17个来源的300个测试任务和3000个候选实现,每个任务都配了经过验证的正确与错误实现面板,核心指标为Joint Success Function(JSF):测试必须在原始代码上失败、接受所有正确实现、拒绝所有错误实现。
研究发现:14种Coding Agent配置中,最强配置的JSF只有28.00%,而同一配置在单一参考评测下的成功率达到59.67%——通过参考答案的测试里超过一半,换一批实现就不再可靠。
研究还对286条失败轨迹做了专家标注,归纳出3大类、7种测试生成错误机制,并提出TestHelix框架,通过异构合成、同伴交叉验证与递归自我改进,在相同推理预算下将JSF提升11.67至12.00个百分点。

测试评测缺了什么?
测试生成评测并不是新问题。已有的Benchmark覆盖了不同粒度的任务:TestGenEval、CLOVER和MultiFileTest盯着函数、文件或多文件级别的测试编写,SWT Bench和TDD Bench Verified则把测试生成搬进真实代码仓库,要求测试复现GitHub Issue里描述的问题。
这些工作普遍沿用同一种成功标准:测试在修复前失败(Fail),在参考修复后通过(Pass),也就是所谓的Fail-to-Pass。标准简洁、可执行,可它暗含一个假设:只要测试能区分「修复前」和「参考修复后」两个状态,就足以判断任意实现对不对。现实里这个假设很难站住。
一方面,测试可能漏掉关键输入,或者断言写得太弱,参考答案能过,有缺陷的实现照样能过;另一方面,测试可能过度依赖参考答案的具体写法,比如固定的内部函数名、输出格式或中间状态,结果其他同样满足需求的实现被误判为错误。
OmniCode、SWE Mutation等工作已经开始引入实现变体,但重点还是看测试能不能拒绝错误变体,「接受正确替代实现」这一侧仍然缺少系统评测。

表1|现有测试生成Benchmark的评测范围与评测目标对比(原论文Table 1)
于是论文抛出的核心问题是:How robust is the evaluation of generated tests to variation across implementations? 说白了,实现方式一变,对测试质量的评价还立得住吗?一份可靠的测试应当像棱镜那样,把同一需求下的不同实现区分开:对正确实现保持宽容,对错误实现保持敏感。
为每个任务配备一组正确与错误实现
为了回答这个问题,研究团队构建了TestPrism。它不像常规做法只给一份参考答案,而是为每个测试任务配备一个候选实现面板(Candidate Panel):1份参考实现、4份正确的替代实现,外加5份错误实现。生成测试时,面板对Agent完全隐藏;到了评测环节,测试要在原始代码和面板中的每一份实现上分别跑一遍。

图1|TestPrism的构建与评测流程(原论文Fig.1)
整套构建流程一共拆成四步走。
第一步,先挑任务、再做适配。
任务来自17个公开Benchmark,SWE-bench系列和Terminal-Bench系列都在其中,SWE PolyBench、SWE rebench、EvoEval、USACO、AlgoTune等也一并纳入,覆盖的场景包括仓库维护、函数规格演化、竞赛编程和数值优化。能被选中的来源,都得有可追溯的测试来源,验证流程也要有据可查。
之后由适配器出手,把各来源的任务统一转成Harbor的任务格式。原始需求、初始代码状态、验证器、参考实现、依赖、服务与执行限制,这些内容在转换中一律保留。
第二步是任务选择。
团队筛任务看三个指标:参考实现的有效代码行数(Solution Size)、测试函数或检查项的数量(Test Count),以及每个被修改文件对应的测试数量(Test Density)。
阈值不是一刀切,而是按每个来源自身的分布来校准,同时用来源配额平衡代表性。最终TestPrism共收入2000个任务:1700个划入训练集,供后文TestHelix做策略优化;剩下300个作测试集。两个集合互不重叠。
第三步,生成候选实现并加以验证。
动手生成之前,团队先复核每个任务原有的验证器:它得在初始状态下失败、在参考实现上通过,才算过关。
两位有开发经验的专家分别独立评估任务需求与验证器,看有没有对参考实现过拟合,或者存在覆盖缺口。意见不一致就讨论澄清,仍然存疑的任务直接剔除(Cohen's κ = 0.68)。
候选实现不是人造的,而是让不同的Coding Agent、能力各异的模型实际去解题得到的。传统变异测试(Mutation Testing)靠人为注入缺陷,这种做法不同,它能同时抓到真实存在的替代解法,以及Agent解题时自然冒出来的错误。
空提交、完全重复的补丁、无法应用的补丁都被清掉,然后在干净环境中跑原有验证器:所有检查全过的,标为正确实现;任意一项检查失败的,标为错误实现。这些标签在后续评测中保持固定。
第四步,做多样性过滤,再把任务转换。
假如4份正确实现只是参考答案的轻微改写,这个面板就没什么意义了。
所以对正确实现组,团队要求满足平均AST距离标准;错误实现组则看未通过的检查项集合,集合不同就算多样,集合相同还得额外过一道AST Jaccard距离检查。面板反复生成、反复替换,直到两组都满足多样性要求为止。
最后,测试生成适配器把每个编程任务转成一份测试编写契约:Agent要依据原始需求,在指定目录中提交测试、Fixture和一个可执行的入口脚本。
数据概览
TestPrism测试集的300个任务横跨Python、JavaScript/TypeScript、Java、C/C++和Go等六种语言,来自67个代码仓库和项目,含3000个带标签的候选实现。
这些任务分成6大类、34个子类:Bug修复167个,程序与算法53个,性能优化28个,功能扩展20个,项目与工作流19个,重构与迁移13个。

图2|TestPrism测试集的任务分类(原论文Fig.7)
评测指标:从单一参考走向联合成功
在此基础上,TestPrism给出了一组互补的评测指标。
Reference Acceptance Rate(RAR),指测试在原始代码上失败、在参考实现上通过的任务比例,也就是传统的单一参考评测;
Valid Acceptance Rate(VAR),指测试接受4份正确替代实现的平均比例;
Invalid Rejection Rate(IRR),指测试拒绝5份错误实现的平均比例;
VAR与IRR的调和平均H,用来判断这两项是否均衡;Change Coverage和Entry Coverage则分别沿用SWT Bench与TestExplora的定义,看测试覆盖了多少参考修改代码以及相关入口代码。
核心指标Joint Success Function(JSF)要求更严:测试得在原始代码上失败,同时接受面板中所有正确实现、拒绝面板中所有错误实现。要是把面板缩到只剩参考实现,JSF就退化成RAR。
换句话说,只看单一参考实现的评测,是JSF最宽松的特例。JSF不会高过RAR,两者之差,对应的正是那些能过参考答案、却扛不住更多实现检验的测试。
RQ1,前沿Coding Agent能写出可靠的测试吗?
第一个研究问题很直接:现在的Coding Agent生成的测试,能不能稳定地把正确实现和错误实现分开?
研究团队评测了来自六个模型家族的14种Coding Agent配置。主表报告每个家族里JSF最高的配置,并优先选用各模型的原生Coding Agent,以体现模型训练与Agent框架之间的紧密耦合。

表2|各模型家族最佳配置在TestPrism上的表现(原论文Table2)
通过参考答案,不等于测试可靠
结果显示,可靠的测试生成还差得远。表现最好的
Claude Fable 5.1在单一参考评测下成功率为59.67%,JSF只有28.00%;Kimi K3、Qwen3.8 Max和GPT 5.6 sol -max的RAR都在54%至57%之间,JSF却只有23%至25%;
DeepSeek V4 Pro和GLM 5.3的JSF更低,分别只有13.33%和11.33%。所有模型都存在同一个问题:不少在传统评测里被判为成功的测试,一碰上更多实现就出错。
接受正确实现,往往比拒绝错误实现更难
再拆开看,六个配置中有五个的IRR高于VAR。也就是说,当前Agent写出的测试更常见的毛病是过严——把参考实现的具体写法当成了需求,误伤其他合法实现。
GLM5.3就是典型:IRR达到86.33%,在所有配置中最高,VAR却只有50.33%,JSF也垫底。
Qwen3.8 Max反过来,VAR最高(77.00%),IRR只有66.00%。只会拒绝错误实现并不够,可靠的测试得同时兼顾宽容与敏感。全部14种配置里,VAR与IRR的调和平均H和JSF同样呈正相关。
覆盖率很重要,但不是全部
覆盖率与JSF也高度相关:全部14种配置中,Change Coverage和Entry Coverage与JSF的Pearson相关系数分别为0.91和0.96。不过,覆盖到相关代码只是测试能正确判断它的前提——测试跑了某段逻辑,不代表它的断言能正确判断这段逻辑的行为。
RQ1的核心发现是:前沿模型距离可靠的测试生成仍有很大差距。接受正确实现和拒绝错误实现,暴露出的是互补的弱点,二者的平衡加上覆盖率,共同影响联合成功率。
RQ2,候选实现越多、越多样,测试越难蒙混过关?
第二个研究问题盯的是候选面板本身:往面板里逐步加入更多、更多样的实现,测试的成功率会怎么变?
研究团队随机抽取50个任务,沿用相同的构建流程,把每个任务的面板从10份实现扩展到15份:1份参考实现、7份额外正确实现和7份额外错误实现。
测试保持不变,从只含参考实现的面板出发,按三种方式逐步加实现:只加正确实现、只加错误实现,以及二者交替加入。

图3|三种实现添加方式下JSF与候选多样性的变化(原论文Fig.3)
结果显示,平均JSF从只有参考实现时的49.33%开始一路往下走:加入5份正确实现后降至38.10%,加入5份错误实现后降至30.49%,交替加入9份实现后降至24.33%;全部实现加入后,三种方式的终点分别是37.33%、29.33%和23.33%。
测试内容没动,变的是面板对测试提出的条件:正确实现越多,越能看出测试是否过严;错误实现越多,越能看出测试是否过松。可见JSF不是固定分数,而是随面板浮动的可靠性度量,面板里放了哪些实现、放了多少实现,分量一样重。只有一份参考实现的单一参考评测,是这一框架里最弱的特例,也是扩展面板成功率的上限。RQ2的核心结论是:候选实现一多,小面板看不出的测试毛病就露出来了。
RQ3,Agent写出来的测试,错在哪儿?
前两个问题只量出了测试“不可靠”到什么程度,没解释原因。研究团队于是从六个模型家族的最佳配置里各取50条JSF为零的失败轨迹,剔除14条超时轨迹后剩286条,交给两位专家各自独立标注(Cohen's κ= 0.81),意见不一致时再请第三位专家一起讨论定论。
标注过程中,专家要核对任务需求、提交的测试、测试在原始代码和参考实现上的执行结果、在各候选实现上的结果,以及对应的补丁证据,最后归纳出3大类、7种具体错误机制。

图4|各模型的指标画像(a)与286条失败轨迹中的错误机制分布(b)(原论文Fig.4)
覆盖缺口:功能测到了,关键输入漏掉了
覆盖缺口(Coverage Gaps)占全部失败的34.6%,其中遗漏输入(Missing Inputs)30.8%、未测试的需求(Untested Requirements)3.8%。遗漏输入是最常见的单项错误之一:测试把某个操作跑了一遍,却没碰到能暴露缺陷的那组输入。比如一个判断扑克牌型的任务,生成的测试始终没比较A当最小牌的顺子和三条谁大,于是一个把这种顺子误判成「高牌」的错误实现就这么过了关。
错误检查:跑到了,判断却错了
错误检查(Faulty Checks)占比最大,50.0%,细分为检查过弱(Weak Checks)7.7%、判断标准错误(Wrong Criteria)26.9%和过拟合(Overfitting)15.4%。
这一类说明,测试就算执行到了相关行为,也未必判得对。过拟合对应的正是RQ1里看到的“过严”:测试把参考实现身上偶然的细节当成了需求本身。
括号解析任务是个例子:测试确实造了括号不匹配的输入,可只数了输出结果有几条,没看具体内容,一个丢掉了合法分组的错误实现因此被放行。
扑克牌和括号解析这两个案例里,测试都在原始代码上失败、在参考实现上通过,放到单一参考评测下都会被算作成功。加了额外的错误实现,那些漏掉的输入组合和过弱的断言才现形。
执行逻辑错误:测试自己先跑不起来
执行逻辑错误(Execution Logic Errors)占15.4%,Fixture与API使用错误7.7%,命令执行与输出解析错误7.7%。在一个EvoEval任务中,测试把被测函数挂在测试类上,再用self去调,结果多绑定了一个参数,测试连参考实现都跑不过。这类问题直接拉低测试的整体可靠性,但和前两类不一样,单一参考评测也能发现,算不上它的“盲区”。
RQ3的核心发现是:测试失败要么是漏了相关行为,要么是判错了实现的对错,要么是测试自己执行不下去;其中遗漏输入组合和过弱断言能骗过参考答案的检验,只有在更多错误实现面前才藏不住。
RQ4,怎么让测试在不同实现之间通用?
针对这些问题,研究团队提出了TestHelix。思路很直接:只拿一份实现对照,发现不了测试的毛病,那就在生成阶段主动造出多份实现,让测试接受多方检验。

图5|TestHelix框架(原论文Fig.2)
异构合成:不同Agent各交一组测试与修复
TestHelix给多个相互隔离的Coding Agent分配不同的探索策略,每个Agent只看公开的任务需求,独立写一份测试T和一份修复P。每对测试与修复都得内部自洽:测试要在原始代码上稳定复现失败,在自己的修复上通过。Agent靠本地执行反馈反复调整,直到满足条件或者预算用完。
同伴交叉验证:拿别人的修复来检验自己的测试
一份测试只在自己的修复上通过,仍可能是一个自洽的错误。TestHelix的办法是让每份测试跑到其他Agent的修复上交叉执行,过于严格的测试或不完整的修复就此暴露。
另外还有一名独立评审Agent,它看不到任何修复代码,只依据公开需求逐条审查断言。每条阻断性意见都必须以公开需求为依据。
评审确认的反例会被用来修正测试与修复。当几份不同的合格修复都能通过已认可的测试时,系统会借鉴差分测试的思路,主动寻找能区分它们的输入。相互兼容的测试随后被合并,并在全新环境中再验证一遍。
递归自我改进:从训练任务里学出更好的测试生成策略
TestHelix从一份基础策略起步,用AutoSaddler做反思式演化:挑一个父策略,诊断它的执行轨迹,提出修订,再在相同任务上比较父子策略的表现,可复用的经验则沉淀下来供后续轮次使用。
优化在TestPrism训练集的600个任务上完成,与测试集完全隔离,也从不接触隐藏的候选面板。优化目标用的是信号更密集的RAR,没有采用更严格的JSF,因为后者往往难以奖励渐进式的策略改进;对多实现区分能力的要求,则交给异构合成与同伴交叉验证来承担。
优化得到的策略被冻结后,直接用于GPT 5.6 sol和Kimi K3两个模型。
实验结果
研究团队在GPT 5.6 sol和Kimi K3上比了五种TestHelix配置,对照是各自的原生Coding Agent(Codex和Kimi Code)。同一模型组内所有配置使用相同的基础模型和工具,单任务推理预算上限也一致。

表3|TestHelix消融实验与原生Coding Agent对比(原论文Table3)
只生成三组测试与修复、再随机提交一份,相比单组生成最多提升1个百分点;同样三组基础上启用完整的同伴交叉验证,JSF提升6个百分点以上,GPT5.6 sol从21.00%升到27.67%,Kimi K3从22.67%升到29.00%。组数从3加到5,只额外带来0.67个百分点;在三组配置上引入递归自我改进,又进一步带来4.33至4.67个百分点的提升。
最终配置在两个模型上分别达到32.33%和33.33%的JSF,相比单组生成提升11.67至12.00个百分点,也比各自的原生Coding Agent高出8.67至9.00个百分点。推理预算固定时,改进「如何生成和检验测试」带来的收益,明显高过单纯把预算分给更多Agent。
RQ4的核心发现是:异构合成让测试能在不同实现之间泛化,同伴交叉验证用多样的修复来检验和修正测试,递归自我改进则在任务之间沉淀测试生成策略,两者在消融实验中都贡献了明确的收益。
下一步是什么?
Coding Agent越来越多地靠测试来验证自己的工作,测试质量的评价标准也得跟着提高。
这项研究说明,单一参考评测会系统性地高估测试质量:一份测试能区分修复前后的两个状态,不代表它能正确判断同一需求下的其他实现。即便最强的前沿模型,面对多份正确与错误实现时,联合成功率也不到三成。
研究同时揭示了问题的两面。测试可能过松,遗漏关键输入或只做表面检查,放过有缺陷的实现;也可能过严,把参考实现的偶然细节当成需求,误伤合法实现。
过松的问题容易在单一参考评测中藏住,过严的问题在单一参考评测里根本无从发现。评测只有同时考察接受与拒绝两个方向,测试的真实可靠性才看得清。
评测怎么做?TestPrism给出的思路是拿一组已经验证过、彼此差异明显的实现来考验测试,而不是死盯一份标准答案,这样更接近真实场景。
方法层面,TestHelix说明提升测试可靠性未必需要更强的基础模型,让多个Agent互相提供反例、互相检验结论,再把有效的经验沉淀为策略,同样能带来显著收益。
论文同时提到,TestPrism里每个任务仅有10份候选实现,正确和错误实现按等比例摆放,这与真实分布并不一致;靠AST衡量的多样性,也没法确保语义上全覆盖。往后要做的,是把评测推向持续变化的需求、更大的实现范围以及更多执行环境。