Skip to content
Go back

造变异体的方法,决定变异测试能找到什么

Edit page
测试研究 · 变异测试 · 三方对比

规则工具、大模型、撤回历史修复实测对比:同一套测试,换一种造法,得分从 3% 变成 96%;真漏洞主要靠大模型造的变异体和撤回的历史修复找到

日期 2026-10-16 代码 Go 新代码 + Python 老代码 变异体 385 个有效 工具假结果 4 种
想找测试漏洞,让大模型造变异体 想守住修过的 bug,撤回历史修复当变异体;规则工具造的变异体只适合当便宜的底线。

我们做了什么:变异测试是故意往代码里放假 bug(变异体),看现有测试能不能发现,没被发现的就是测试的漏洞。假 bug 有三种造法:规则工具机械地改符号,大模型先读测试再挑盲区下手,撤回历史修复把以前修好的 bug 放回去。我们拿同一套现有测试(Go 新代码 + Python 老代码),三种造法各跑一遍。

发现:

  • 大模型造的变异体最能找到真漏洞:每 100 个里有 28 个(Go)和 39 个(Python)指出了真漏洞。
  • 撤回历史修复得到的变异体,是唯一找回两个已知漏洞的;但代码一变,修复就很难撤回去。
  • 规则工具在 Go 上造了 178 个变异体,只有 2 个指出了真漏洞。
  • 同一套测试,对规则工具造的变异体得分 96%,对大模型造的变异体只有 3%–6%。测试没变,变的是变异体的难度。
  • 两个变异测试工具都给过假结果,不处理会得出错误结论。

结论:想找测试的盲区,让大模型造变异体;想守住修过的 bug 不再坏,撤回历史修复当变异体;规则工具造变异体便宜,适合当底线。比较造法时数真漏洞,不要比分数;分数只适合在同一种造法下看前后变化。

28 / 39大模型造的每 100 个变异体里,指出真漏洞的个数(Go / Python)
2 / 2已知漏洞,只有撤回修复造的变异体找回
96% vs 3%同一套测试的得分,只因造法不同
4种工具假结果

先说清楚:变异测试是在检查测试

平时写的单元测试是保安,每天都在岗。变异测试是突击检查:故意往代码里放一个小 bug(叫「变异体」),看保安会不会发现。测试变红叫「杀死」,没变红叫「存活」。杀死的比例就是变异得分。

变异得分(mutation score)= 被杀死的变异体数 ÷(变异体总数 − 等价变异体数)。它衡量的是测试集发现这批变异体的能力。

— 定义见 Jia & Harman 的变异测试综述 [18];该定义可追溯到 DeMillo、Lipton、Sayward 1978 年提出变异测试时的说法。

注意分母里是「这批变异体」。定义本身就说明了:分数是测试和变异体两方面一起决定的。研究也发现,扣掉测试集大小这个因素以后,变异得分和发现真实 bug 的能力只是弱相关 [2]。

本文的算法稍有不同:分母用的是「有效变异体」(能编译、能跑的),没有先扣掉等价变异体。原因是等价要逐个人工判定,我们只对存活的做了判定。所以表里的得分比按定义算的略低。比如 Go 规则组扣掉 3 个等价后是 171 ÷ 175 ≈ 98%。

变异测试本身不留在仓库里。它的价值是找出测试没守住的地方,然后你再补一条普通测试。

这次研究的起因是之前做的一个小试点:我们把 20 个已修 bug 的修复撤回去,看现有测试会不会报警。修复时带了测试的 15 个全部被抓住,没带测试的 4 个只抓住一半。那次的结论讲的其实是「普通测试有没有用」。这次我们把重点放回变异测试本身:造变异体的方法不同,结论会差多少?

三种造变异体的方法

还是那个比方:同一个保安,换三种人去试探他。

夜间博物馆里一名保安守着展柜,三个闯入者用不同方式试探他:左边一个随手碰倒东西,中间一个躲在柱子后观察保安,右边一个照着旧图纸重演以前的盗窃
左:随手乱挪的人(规则工具)。中:躲在柱子后先看保安站位的人(大模型)。右:照着旧案图纸再偷一次的人(撤回历史修复)。保安始终是同一个——仓库里现有的测试。

规则工具随手乱挪

工具按固定规则改代码:== 改成 !=,&& 改成 ||,continue 改成 break。量大、便宜、全自动。Go 用 gremlins,Python 用 mutmut。

大模型先看站位再下手

把代码和相关测试一起交给大模型,让它造「它认为现有测试抓不住」的真实型 bug,比如写错 ID、少个保护条件、状态记错。参考 2026 年 test-aware 变异的做法。每个文件 12 个。

撤回修复照旧案再偷一次

把历史上的 bug 修复反向打回去,得到的是一个真实发生过的 bug。打不上的(代码已经变了),交给大模型按意思移植成最小补丁。

实验怎么做

两段代码
  • Go 新代码:一个上下文引擎包里修 bug 最多的 3 个文件,约 1,230 行,2026 年 7 月才写的。包内测试跑一遍约 27 秒。
  • Python 老代码:定时任务调度模块的 3 个文件,约 1,890 行,2025 年 12 月就有了,修过 30 次。相关测试 236 条,跑一遍约 5 秒。
  • 基线选在补测试之前的提交,所以试点里发现的两个已知漏洞还在,可以当标准答案。
怎么判定
  • 只认具名测试失败或测试超时算杀死;编译、导入失败记「无效」。
  • 每个存活的变异体交给一个独立评审(另一个大模型 agent),它不知道变异体是谁造的、也不知道生成方的预测,分成三类:真漏洞、无关紧要、等价(行为没变)。
  • 评审校准:把两个已知漏洞混在存活变异体里,不告诉评审。它两个都判成了「真漏洞,高置信」。

结果

先看形状:同一套测试,两张图里的线几乎上下颠倒。得分在「大模型造的变异体」这一格掉到谷底,真漏洞恰恰在这一格冲到最高。

变异得分(越高 = 现有测试抓住得越多)
0255075100规则工具大模型撤回修复96%6%80%56%3%95%
每 100 个变异体里的真漏洞(越高 = 越能找出盲区)
010203040规则工具大模型撤回修复1.128205.7395.3
Go 新代码Python 老代码

分数低说明测试没抓住,没抓住的正是要找的盲区,所以两条线反着走。下表是完整数字。

组有效杀死存活真漏洞无关紧要等价变异得分每 100 个的真漏洞
Go · 规则178171722396%1.1
Go · 大模型36234101956%28
Go · 撤回修复108220080%20
Py · 规则(抽样)10659476291256%5.7
Py · 大模型36135149123%39
Py · 撤回修复1918110095%5.3

Python 规则组是从 mutmut 造的 885 个里按文件各抽 40 个。Python 撤回修复组:30 个修复里自动撤回 3 个、移植 16 个,另 11 个牵涉已删除的代码,没有做。

存活的里面,有多少是真漏洞

13% – 29%
规则工具造的变异体(Py / Go)
29% – 40%
大模型造的变异体(Go / Py)
3 / 3
撤回修复造的变异体:存活的全是真漏洞

已知漏洞找回了几个

方法漏洞一:子代理记录证据时记错身份漏洞二:删会话等锁没有上限
规则工具造的没造出来没造出来
大模型造的不算数:大模型能读到仓库历史,看到这两处后来补了测试,故意避开了同左
撤回修复造的✓ 存活✓ 存活

三种方法找到的是不是同一批漏洞

  • Go:规则工具造的变异体指出的 2 个真漏洞,大模型造的变异体也都指出了;大模型造的另外指出 8 个规则工具没碰到的。撤回修复组的 2 个,另外两组都没有。去重后共 12 个。
  • Python:规则组 6 个、大模型组 14 个,只有 1 处重叠。撤回修复组唯一存活的那个,和大模型造的某一个变异体是同一个改动。去重后共 19 个。

一句话:现有测试对规则工具造的变异体得分最高,但这组指出的真漏洞最少;对大模型造的变异体得分最低,这组指出的真漏洞却最多。变异得分只有在说明了「变异体从哪来」之后才有意义。

大模型造的变异体指出了哪些漏洞

挑几个评审判为高置信的,只描述行为。

改动后果
中止判断里多加了一个「结果为空」的条件被中止的定时任务又会被记成「成功」。代码注释里记着,这正是一次线上事故的形状。
执行锁按「任务」而不是按「员工」同一个员工的两个定时任务会并发执行,违背文档约定的串行。
重新注册任务时不更新会话 ID改过会话的任务继续往旧会话发。撤回修复组也撞到了同一处。
证据工具去掉等锁的 3 秒上限后台重建期间,一轮对话会一直卡着。
已核实的证据走进默认分支模型被告知「已记为推测」,拿到错误引导。规则工具也翻出了这一处。

大模型造的变异体噪声也不少:Go 存活的 34 个里,19 个被判「无关紧要」,多是错误分支和极少走到的组合。所以大模型组离不开评审这一步,否则 34 个存活没法解读。

工具的坑:四种假结果

这一节比上面的数字更重要。每一种不处理,都会直接得出错误结论。

现象看上去像实际是怎么防
gremlins 6 秒跑完 160 个,全部杀死 测试很强 它把代码复制到临时目录跑,go.mod 里用相对路径替换的本地依赖找不到,每次都编译失败,被算成杀死 耗时不合理就是假的。实验副本里把替换改成绝对路径
gremlins 报 19 个「没有覆盖」,不跑 这些行没测试 switch 里 case 后面的条件不进 Go 覆盖率统计,这些行其实每次都跑。补跑后 11 个杀死、7 个存活 「没覆盖」的要看是不是 case 条件,必要时自己生成补丁跑
mutmut 2.5.1 跑了 291 个,全部存活 测试很弱 同一个变异体手动跑,测试是红的。问题在工具调度,不在测试 只用它造变异体,用自己验证过的脚本跑
自己的脚本把一个变异体判成存活 发现了新漏洞 测试卡死,跑满 10 分钟超时,输出里没有「测试失败」那一行 超时单独记一类,算作检测到

规则:全杀死和全存活一样可疑,先怀疑工具。只认具名测试失败;耗时、计数和日志对不上时,手动复现一个再说。

关于变异测试,学到了什么

1
变异得分不能跨方法比较
同一套测试,对规则工具造的变异体得分 96%,对大模型造的只有 6%。分数只在同一种造法下才能比:用来看测试改动前后的趋势可以,跨方法比较不行。报分数时要同时说明变异体从哪来。
度量
2
规则工具造的变异体得分高,因为它们便宜好抓
测试写得密的代码,简单的运算符翻转几乎都会被抓住。Go 那组留下的 7 个里,3 个只改了一个最后会被丢弃的返回值。
规则
3
大模型先看测试再造变异体,指出真漏洞最多,噪声也多
得分低是设计使然:它专挑测试抓不住的地方下手。存活的里约 30%–40% 是真漏洞,剩下的要靠评审筛。
大模型
4
撤回修复造的变异体最准,但会随时间衰减
存活即真漏洞,也是唯一能找回「已经出过的 bug」的方法。代价是移植:Go 同一天密集修的 10 个只有 1 个能自动撤回,Python 老代码 30 个只有 3 个。大模型按意思移植后,25 个全部可用。
撤回修复
5
独立评审必不可少,而且可以校准
把已知漏洞混进去检验评审。没有这一步,大模型组的结果没法解读。
评审

成本

环节大致量
规则工具 · Go 全量约 3 分钟(2 个并行)。被杀死的大多很快就失败退出
规则工具 · Python 抽样 120 个约 15 分钟
大模型造变异体每种语言 36 个,约 16–18 万 token,4–6 分钟
移植撤不回去的修复Go 9 个约 15 万 token,Python 16 个约 17 万 token
独立评审每批存活约 11–15 万 token
跑存活的变异体存活的要跑满整套测试:Go 每个 30–50 秒,一次卡死跑满 10 分钟

评审的工作量和存活数成正比。规则工具在 Python 上抽 120 个就要判 47 个,其中大部分无关紧要。

怎么用到日常流程

PR 上 · 规则工具
  • 只跑改动的文件,当便宜的底线。
  • 注意 case 条件的「没覆盖」和相对路径依赖这两个坑。
高风险改动 · 大模型
  • 让大模型先看测试,再造 3–5 个变异体。
  • 存活的交给独立评审,只把「真漏洞」给作者看。
修 bug 时 · 撤回修复
  • 当场撤掉修复,确认新测试会红。越晚越撤不回去。
  • 定期抽查修复时没带测试的那些。

局限

  • 每种语言只有 3 个文件;Python 规则组是抽样。
  • 造变异体的和评审的是同一类模型,可能有共同的偏好;评审只用两个已知漏洞校准过。
  • 造变异体的大模型能读到仓库历史,所以「已知漏洞召回」这一项对大模型组无效。下次要在看不到后续提交的代码上生成。
  • 「真漏洞」是评审的判断,不是线上真的出过的 bug(撤回修复组除外)。

一句话:规则工具造的变异体适合当便宜的底线,大模型造的适合找新漏洞,撤回修复适合守住「修过的不再坏」。三者互补,不能互相替代。用任何一个之前,先确认工具给的结果是真的。

附:变异测试研究速览

做试点之前查过的资料,按问题归类,每条只留结论。

变异得分本身靠不靠谱

研究结论
Just 等,FSE 2014 [1]357 个真实 bug:排除覆盖率的影响后,杀死变异体的能力和发现真实 bug 的能力仍然显著相关。
Papadakis 等,ICSE 2018 [2]控制住测试集大小后,这种相关性变弱。但测试集大小相同时,变异得分高的那组确实能发现更多真实 bug。所以变异体适合用来指导补测试,分数本身不是精确的质量刻度。
Zhao 等,ISSTA 2026 [3]在大模型写的测试上复现:被测代码没有 bug 时,覆盖率和变异得分可以用来比较模型;被测代码已经有 bug 时,两者都不可靠。

工业界怎么用

来源做法与结果
Google [4][5]只在代码评审的改动上跑,跳过日志这类没意义的行,只把存活的变异体指给作者看,不给总分。研究分析了近 1500 万个变异体:用了变异测试的开发者写的测试更多、质量更好;回看高优先级的真实 bug,如果当时跑过变异测试,会有一个存活的变异体提前报警。
Meta ACH,FSE 2025 [6]针对隐私问题生成变异体,再让大模型写测试去杀死它们。工程师接受了 73% 的测试;571 个测试里有 277 个,如果只看行覆盖率会被丢掉。

用大模型造变异体

研究结论
LLM 变异测试综合研究 [7]大模型造的变异体更像真实 bug,检出率约 77%,规则工具约 42%。代价是编译不过、重复和等价的变异体都更多。
SMART,FSE 2026 [8]从约 13 万对真实修复里检索例子再生成:有效生成率从 42.89% 升到 65.6%,真实 bug 检出率 92.61%;7B 小模型加上这个方法,可以追平或超过 GPT-4o。
Test-aware 变异,ESEM 2026 [9]把现有测试也交给大模型,让它造一个能骗过这些测试的 bug。骗过现有测试、又被完整测试抓住的比例:HumanEval 87.7%,MBPP 79.1%;同条件下 mutmut 只有 4.4% 和 5.7%。

大模型写的测试质量如何

研究结论
单元测试生成综述 [10]引用的结果:大模型写的测试变异得分最高 0.546,人写的是 0.690;有的研究里接近 0,因为测的是接口、空方法这类没有逻辑的地方。
软件演化研究,2026 [11]8 个模型、22,374 个程序变体:代码语义改变后,新测试通过率降到 66%;失败的测试里超过 99% 在旧代码上能通过。
Python 类级测试,2026 [12]覆盖率几乎都到顶,分不出好坏,变异得分能拉开差距;选哪个模型比怎么写提示词影响更大。
SWE-Mutation,2026 [13]最强的模型检出率只有 36.15%。但评审指出这些变异体没有和真实 bug 对照过,结论还不能算定论。

把存活的变异体交回给大模型

研究结论
MuTAP [14]把存活的变异体写进提示词,让模型重写测试,HumanEval 上变异得分约 94%。只在小基准上验证过。
AdverTest,2026 [15]一个 agent 写测试,一个 agent 造变异体,两边对抗。Defects4J 上缺陷检出率比最好的 LLM 方法高 8.56%,比 EvoSuite 高 63.30%。
Critic 循环实验,2026 [16]迭代修改能杀掉初始测试漏掉的变异体中的 78%。作者还发现,自己早先一个极显著的结果其实是模型输出被截断造成的。评测工具本身也会出错。
实践文章,2026-08 [17]四个项目都没改生产代码,变异得分全部上升。其中一个解析器行覆盖率 100%,变异得分只有 61.29%。作者的建议:不要让 agent 去优化分数;把存活的变异体当作反例,写一个在原代码上通过、在变异代码上失败的测试;同时把需求交给 agent。

一句话:变异测试适合用来指出测试漏在哪里,不适合拿来打分。大模型造变异体已经比规则工具更像真实 bug,但它写的测试容易照着现有代码写断言。最好的用法是让大模型出题,测试是否真的有效要另外验证。

参考资料

  1. Just et al. Are mutants a valid substitute for real faults in software testing? FSE 2014.
  2. Papadakis et al. Are Mutation Scores Correlated with Real Fault Detection? ICSE 2018.
  3. Zhao, Zhou, Cohen. Do Coverage and Mutation Scores of LLM-Generated Test Suites Correlate with Their Effectiveness? ISSTA 2026.
  4. Petrović, Ivanković. State of Mutation Testing at Google. ICSE-SEIP 2018.
  5. Petrović et al. Does mutation testing improve testing practices? ICSE 2021.
  6. Foster et al. Mutation-Guided LLM-based Test Generation at Meta. FSE 2025 Industry.
  7. A Comprehensive Study on Large Language Models for Mutation Testing. arXiv 2406.09843.
  8. Wang et al. Boosting LLMs for Mutation Generation (SMART). FSE 2026.
  9. Kiele et al. Beyond Rule-Based Mutation Testing: Test-Aware Mutant Generation Using LLMs. ESEM 2026.
  10. Large Language Models for Unit Test Generation: Achievements, Challenges, and the Road Ahead. arXiv 2511.21382.
  11. Haroon, Khan, Gulzar. Evaluating LLM-Based Test Generation Under Software Evolution. arXiv 2603.23443.
  12. Al-Ahmad et al. Evaluating the effectiveness of class-level LLM-generated test suites in Python. arXiv 2609.24341.
  13. Sun et al. SWE-Mutation: Can LLMs Generate Reliable Test Suites in Software Engineering? arXiv 2605.22175.
  14. Dakhel et al. Effective Test Generation Using Pre-trained LLMs and Mutation Testing (MuTAP). arXiv 2308.16557.
  15. Chang et al. Test vs Mutant: Adversarial LLM Agents for Robust Unit Test Generation. arXiv 2602.08146.
  16. Otterson. Adversarial Test-Hardening for AI-Written Code. arXiv 2607.23002.
  17. Mutation Testing for Agent-Written Code. awesome-testing.com, 2026-08.
  18. Jia, Harman. An Analysis and Survey of the Development of Mutation Testing. IEEE Transactions on Software Engineering 37(5), 2011.
数据来自一次内部实验:Go 与 Python 混合代码库,2026-10。

Edit page
Share this post on:

Previous Post
Why Claude Dispatch and Grok's Primary Bot Point to the Next Agent Design
Next Post
Claude Code 的 spawn_task 和 show_widget 是做什么的