Coding Agent测试能力被高估1倍:合格率集体失败
新智元报道 Coding Agent写代码的能力越来越强,写测试的能力也在同步提升。从为函数补单元测试、为GitHub Issue编写复现用例,到为整个代码仓库构建回归测试,Codex、Claude Code等前沿Coding Agent已经能够浏览仓库、修改实现,并借助执行反馈不断修正测试。测试的地位也随之变化:它不
新智元报道
新智元报道
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
测试生成评测并非新话题。
已有的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,两者之差对应的正是那些能过参考答案、却经不起更多实现检验的测试所占的比例。
第一个研究问题就是:当前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%,命令执行与输出解析错误也占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