Coding agent写测试集体失败!Al代码合格率被高估一倍
Coding agent写测试集体失败!Al代码合格率被高估一倍
新智元报道
新智元报道

Codex、Claude Code这类前沿Coding Agent写代码的本事见长,写测试的水平也跟着往上走。补函数单元测试、给GitHub Issue写复现用例,乃至为整个代码仓库搭回归测试,它们都能浏览仓库、改动实现,再靠执行反馈反复调整测试。
测试的角色因此变了味。它不再只是软件质量的把关手段,还成了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。它不只给一份参考答案,而是为每项测试任务准备了一个候选实现面板(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,两者之差正好对应那批能过参考答案、却经不起更多实现检验的测试所占的比例。
第一个研究问题:当前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的核心发现是:前沿模型距离可靠的测试生成仍有很大差距,接受正确实现与拒绝错误实现暴露出互补的弱点,二者的平衡加上覆盖率共同影响联合成功率。
第二个研究问题针对候选面板本身:往面板里逐步加入更多、更多样的实现,测试成功率会怎么变?研究团队随机抽取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的核心结论是:候选实现足够多样,才能让小面板藏住的测试错误现形。
前两个研究问题量出了测试“不可靠”的程度,却没解释不可靠的成因。
研究团队于是从六个模型家族的最佳配置里各取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%。比如一个EvoEval任务中,测试把被测函数挂在测试类上,再经self调用,结果多绑定了一个参数,测试连参考实现都过不了。这类问题会直接拖低测试的整体可靠性;不过和前两类不同,单一参考评测同样能发现它们,算不上单一参考评测的“盲区”。
RQ3的核心发现是:测试失败要么因为漏掉了相关行为,要么因为误判了实现是否正确,要么因为测试自身执行出错;其中遗漏输入组合和过弱断言能骗过参考答案的检验,只有在更多错误实现面前才会露馅。
围绕这个问题,研究团队提出了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的多样性度量也保证不了语义层面的完全覆盖。把评测扩展到不断演化的需求、更广的实现空间和更多执行环境,仍是未来值得探索的方向。
参考资料:
https://doi.org/10.48550/arXiv.2610.12289