Coding agent写测试集体失败!AI代码合格率被高估一倍
sina qqZone renren ydCloud 0

新智元报道
![]()
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个百分点。

论文:https://doi.org/10.48550/arXiv.2610.12289
研究数据与代码:https://github.com/TestPrism/TestPrism
测试评测缺了什么?
测试生成评测并不是新问题。
TestGenEval、CLOVER和MultiFileTest关注函数、文件或多文件级别的测试编写,SWT Bench和TDD Bench Verified则把测试生成带到真实代码仓库,要求测试复现GitHub Issue中描述的问题。已有的Benchmark覆盖了不同粒度的任务。
它们普遍沿用一种成功标准:测试在修复前失败(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。与只给一份参考答案不同,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份错误实现的平均比例;
H值取VAR与IRR的调和平均,用来判断两者是否处在平衡状态;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呈正相关。
覆盖率也不能忽视。全部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个百分点的提升。
最终配置在两个模型上的JSF分别是32.33%和33.33%,比单组生成高11.67至12.00个百分点,也比各自的原生Coding Agent高8.67至9.00个百分点。推理预算固定的情况下,改进「如何生成和检验测试」拿到的收益,明显高过单纯把预算分给更多Agent。
RQ4的核心发现是:异构合成让测试能在不同实现之间泛化——同伴交叉验证靠多样的修复来检验和修正测试,递归自我改进则在任务之间把测试生成策略沉淀下来,两者在消融实验里都贡献了明确的收益。
下一步是什么?
Coding Agent越来越依赖测试来验证自己的工作,测试质量的评价标准也得跟着往上提。
这项研究说明,单一参考评测会系统性地高估测试质量:一份测试能区分修复前后的两个状态,不等于它能正确判断同一需求下的其他实现。就算是最强的前沿模型,面对多份正确与错误实现时,联合成功率也不到三成。
研究同时把问题的两面摆了出来。测试可能过松,遗漏关键输入或者只做表面检查,把有缺陷的实现放过去;也可能过严,把参考实现的偶然细节当成需求,误伤合法实现。
前一种情况容易在单一参考评测里藏住,后一种在单一参考评测里根本发现不了。评测只有同时看接受和拒绝两个方向,测试的真实可靠性才看得清。
从评测角度看,TestPrism给的检验方式更贴近实际:拿一组经过验证、有多样性的实现来检验测试,不只依赖一份标准答案。
TestHelix 这套做法其实指向一件事:想让测试更可靠,未必要靠更强的基础模型。多个 Agent 相互给反例、彼此核查结论,再把管用的经验固化成策略,收益一样不小。
当然,论文也指出,TestPrism每个任务只包含10份候选实现,正确与错误实现等比例配置并不代表真实分布,基于AST的多样性度量也保证不了语义层面的完全覆盖。把评测扩展到不断演化的需求、更广泛的实现空间和更多执行环境,仍是未来值得探索的方向。
参考资料:
https://doi.org/10.48550/arXiv.2610.12289
