跳转到主要内容

大模型驱动的测试生成:利用 AI 发现软件中的 Bug,而不仅仅是编写代码

阅读需 2 分钟Tian PanTian Pan

大多数使用 LLM 的工程团队都专注于代码生成 —— 让模型更快地编写功能。但有一个杠杆率更高、受关注度却低得多的应用:使用 LLM 生成能发现人类遗漏的 bug 的 测试。不是测试 AI —— 而是 AI 测试你的软件。

这个想法非常诱人。手动编写的测试套件受限于人类的想象力,这意味着它们往往集中在开发者能想到的场景中。LLM 探索状态空间的方式则完全不同。它们生成的输入和边界情况对于原始作者来说往往感觉很陌生 —— 而这恰恰是未被发现的 bug 潜伏的地方。

但现实比愿景要复杂得多。原生 LLM 生成的测试有一半以上的时间无法通过编译。超过 85% 的失败源于错误的断言。而且,将非确定性的生成过程集成到确定性的 CI 流水中,本身就会产生一系列工程难题。以下是让它真正发挥作用的方法。

人类无法触及的状态空间

传统的测试套件存在一个根本性的偏见:它们测试的是开发者在编写代码时预想的场景。像 Hypothesis 和 QuickCheck 这样的基于属性的测试(Property-based testing)框架通过生成随机输入提供了一些帮助,但它们仍然需要人类来定义被测试的属性。LLM 填补了这一空白,因为它们既能理解代码的意图,也能理解可能破坏代码的输入类型。

实际证据支持了这一点。Meta 的自动化合规加固(ACH)系统使用 LLM 生成真实的故障变体 —— 即反映开发者实际错误(而非合成的基于规则的转换)的变异。然后,该系统生成能够确保捕捉到这些特定故障的测试。该系统已部署在 Facebook Feed、Instagram、Messenger 和 WhatsApp 中,据工程师报告,它捕捉到了传统测试套件系统性遗漏的回归类错误。

核心洞见在于,LLM 擅长一项历来成本高昂的任务:生成具有语义意义的测试输入。传统的变异测试产生的故障虽然在语法上正确,但往往不切实际。LLM 产生的故障看起来更像是疲惫的开发者真正会犯的错误 —— 比如业务逻辑中的正负一错误(off-by-one errors)、缺少可选 API 字段的空检查、或者重构后错误的枚举处理。

测试先知问题:你最大的障碍

生成测试输入是简单的部分。难点在于“测试先知”(Oracle)问题 —— 即知道正确的输出应该是什么。当 LLM 生成测试用例时,它需要对预期行为做出断言。而这正是出问题的地方。

研究一致表明,错误的断言是 LLM 生成测试失败的主要模式。研究发现,原生生成的测试的通过率往往低于 50%,在某些基准测试中,断言错误占失败原因的 85% 以上。模型可能会生成看起来合理但语义错误的断言 —— 它根据训练数据中的模式“幻觉”出预期值,而不是根据实际的程序语义进行推理。

这里有三种处理该问题的实用策略:

  • 变异引导生成。与其要求 LLM 预测输出,不如将变异测试作为“先知”。生成一个变异体(代码的错误版本),然后生成一个能区分变异体和原始代码的测试。这里的“先知”是隐性的:测试应该在原始代码上通过,并在变异体上失败。这是 Meta 的 ACH 系统采用的方法,它完全避开了断言质量问题。

  • 思维链断言生成。TestChain 和 ChatTester 等工具指示 LLM 首先逐步分析方法的逻辑,计算给定输入的预期输出,然后再编写断言。这通过强制模型在确定预期值之前展示其推导过程,减少了逻辑幻觉。

  • 基于共识的先知。多智能体方法让多个 LLM 实例就正确行为应该是什么进行讨论,并基于共识生成断言。这可以捕捉到单个模型自信地产生错误断言的情况。

这些方法都不是完美的。实际的建议是将 LLM 生成的断言视为需要验证的假设,而不是绝对的事实真理。

真正行之有效的混合架构

无论是 LLM 还是传统工具都无法完胜。研究表明,它们遗漏的代码路径各不相同 —— LLM 擅长语义输入生成,而像 EvoSuite 这样基于搜索的工具则能实现更系统的覆盖。获胜的方法是将两者结合。

CodaMosa 证明了这一点,它使用 LLM 生成种子测试用例,帮助进化搜索算法跳出局部最优解。当基于搜索的工具陷入困境 —— 无法通过随机变异增加覆盖率时 —— LLM 会生成一个新的测试用例,触及之前未覆盖的代码。然后,搜索算法将此作为进一步探索的新起点。

一个实用的混合架构如下所示:

  1. 运行现有测试套件,建立基准覆盖率并识别未覆盖的代码区域。
  2. 将未覆盖的区域连同完整的类上下文提供给 LLM —— 包括依赖项、类型和接口。研究表明,42% 的生成失败源于缺失外部上下文,因此提供完整的上下文至关重要。
  3. 激进地过滤生成的测试。编译、执行、检查多次运行中的不稳定性(flakiness),并衡量每个测试是否贡献了新的覆盖率。丢弃所有没有价值的测试。
  4. 将幸存的变异体作为目标。对你的组合套件运行变异测试,然后将幸存的变异体反馈给 LLM,进行有针对性的测试生成。

这种迭代循环 —— 生成、验证、变异、再生成 —— 可以将通过率从 50% 以下提升到 70% 以上,并捕捉到任何单一方法都无法独立发现的 bug。

如何在不破坏构建的情况下在 CI 中应用

核心冲突:LLM 生成的测试是非确定性的,而 CI 流水线需要确定性的行为。相同的提示词在不同运行中可能产生不同的测试。以下是团队解决这一问题的方法。

离线生成,确定性运行。 不要在 CI 构建过程中调用 LLM。相反,将测试生成作为一个单独的、定时任务运行——例如每晚或每周。生成的测试在作为常规、确定的测试文件提交到测试套件之前,需要经过验证流水线(编译、执行、不稳定性检测、覆盖率分析)。一旦提交,它们就像其他测试一样运行。

实施多阶段验证流水线。 每个生成的测试在进入套件之前必须通过以下关卡:

  • 编译检查 —— 立即拒绝语法无效的测试。
  • 在当前代码上执行 —— 测试必须在生成它时所针对的代码上通过。
  • 不稳定性检测 —— 将测试运行 N 次(通常为 5-10 次),并拒绝任何在多次运行中产生不同结果的测试。
  • 覆盖率贡献 —— 衡量该测试是否覆盖了尚未覆盖的代码路径。丢弃冗余测试。
  • 变异得分 —— 验证测试是否真正杀死了至少一个变异体,以确认其具有真实的错误检测能力。

严格追踪误报率。 决定采用成败的指标不是覆盖率或错误数量,而是生成流水线的误报率。如果工程师开始看到 LLM 生成的测试无故失败,信任会迅速瓦解,整个努力都会被放弃。Meta 的 ACH 系统投入大量资源确保生成的测试是可靠的,正是因为误报是推广的毒药。

预留 API 成本预算。 每个测试生成循环都涉及多次 LLM 调用——上下文收集、生成、验证反馈、重新生成。在大规模情况下,这笔费用会累积。NVIDIA 的 HEPH 框架报告称,每个试点团队最多可节省 10 周的开发时间,但投资回报率 (ROI) 计算需要考虑推理成本,尤其是当你正在对大型代码库生成测试时。

LLM 尚不能测试的内容

LLM 驱动的测试生成并非银弹。在以下类别中,它的表现始终欠佳:

  • 具有复杂状态的集成测试。 LLM 难以设置真实的数据库状态、正确模拟外部服务,或推导依赖于先前系统状态的多步工作流。它们生成单元级测试的可靠性远高于集成测试。

  • 性能和负载测试。 生成功能测试用例是触手可及的,但生成有意义的性能基准测试需要理解生产部署拓扑、预期的负载模式和可接受的延迟预算——这些上下文在代码库中很少见。

  • 安全关键型断言。 虽然 LLM 可以生成检查常见漏洞模式的测试,但不应将它们作为安全关键代码路径的唯一真理来源 (Oracle)。遗漏安全错误的成本远高于手动编写测试的成本。

  • 需要领域专业知识的测试。 金融计算、医疗方案、法律合规检查——任何正确性标准需要专业知识的领域,而这些知识在训练数据中可能没有得到很好的体现。

最佳做法是将 LLM 生成的测试作为人工编写测试的补充,而非替代品。让模型处理对边缘情况和边界条件的乏味探索,而工程师则专注于需要判断力和领域知识的测试。

如何从小处着手(而不必大费周章)

你不需要 Meta 那样的基础设施就能开始使用 LLM 进行测试生成。这里有一个最小可行的方法:

  1. 选择一个测试覆盖率低的模块。 不要尝试在整个代码库中生成测试。从一个具有核心业务逻辑且现有覆盖率较差的模块开始。

  2. 明智地使用模型的上下文窗口。 包含源文件、其直接依赖项、现有测试(作为风格和断言模式的示例)以及任何相关的类型定义。上下文越多,幻觉产生的符号就越少。

  3. 从回归测试开始,而不是规范测试。 让 LLM 生成验证当前行为而非预期行为的测试。这规避了真理来源问题——当前代码 就是 真理来源。你会捕捉到回归错误,而不是原始 Bug,但这仍然很有价值。

  4. 像审查初级开发人员的代码一样审查生成的测试。 模型生成的测试在结构上可能是合理的,但偶尔会做出错误的断言。流程中人工审查是必不可少的。

  5. 衡量覆盖率增量,而不是绝对覆盖率。 追踪每一批生成任务增加了多少额外覆盖率。如果数字进入平台期,请更改你的上下文策略或针对不同的模块。

从 LLM 驱动的测试生成中获益最多的团队将其视为一种增强工作流,而非自动化工作流。LLM 提出建议,工程师进行处理,两者的结合产出一个能探索到双方都无法单独触及的角落的测试套件。

参考资料

保持联系,关注我获取更多内容

阅读需 8 分钟

你在廉价模型上测试,却在昂贵模型上部署

在廉价模型上运行 CI 和评估,而生产环境却使用尖端模型,这在最关键的地方破坏了开发与生产环境的一致性。本文将探讨为什么层级差距会使你的评估门控失效,以及缩小这一差距的预算计算方法。

insider
llm
阅读需 11 分钟

Prompt 修改不只是措辞变动:将 Prompt 视为软件的代码审查规范

Prompt 修改看起来像是英语,但行为表现却像代码。通过配对评估与 Prompt 的 PR、行为差异注释以及划分审查角色等规范,在用户发现之前捕捉行为回归。

insider
llm
阅读需 11 分钟

将 Eval 作为 Pull Request 评论而非任务:在代码审查中嵌入 LLM 质量门禁

为什么只有当 LLM 评估(evals)存在于 diff 旁边的 PR 评论中时,才能有效捕捉回归。借鉴代码覆盖率如何从夜间任务迁移到内联审查界面的经验 —— 以及将“评估即任务”转变为“评估即合并门禁”的四个工程关键点。

insider
llm
阅读需 12 分钟

Agent 测试金字塔:为什么 70/20/10 的分层对 Agentic AI 行不通

经典的单元/集成/端到端测试金字塔建立在廉价、快速、确定性单元的假设之上。而 LLM Agent 打破了所有这些假设。本文探讨真正可行的测试策略是什么样的。

insider
ai-agents
阅读需 11 分钟

如何在 CI 中对 AI Agent 工作流进行集成测试,而无需完全 Mock 模型

一种针对 AI Agent 的三层 CI 测试架构,既能避免实时 API 调用产生的成本,也能避免完全 Mock 模型带来的空洞感 —— 通过使用 StubLLM 测试替身、VCR 录制回放以及工具契约测试,在编排 Bug 进入生产环境前将其捕获。

insider
ai-agents