如何用 AI 生成测试:先定 oracle,再用受控故障验证

如何用 AI 生成测试:先定 oracle,再用受控故障验证

Olivia Park
2026年8月24日· 9 分钟阅读

要用 AI 生成测试并让它们更有用,应从需求和独立的测试 oracle 出发,而不是只看实现。先要一份正常、边界和失败场景的矩阵,再审查 fixture、mock、断言和副作用,并证明每个重要测试在行为被故意破坏时会失败。

目标是证据,而不是更多的测试数量。用在受控边界内使用 AI的流程控制范围,并把每个生成的测试当作必须赢得信任的代码。

关键要点

  • 生成测试前,先冻结需求和基线行为。
  • 定义什么可观察的结果会让每个用例通过或失败。
  • 在要求测试代码之前,先建立场景矩阵。
  • 审查 fixture、mock、断言、清理和不确定因素。
  • 用受控故障证明测试确实能发现它声称保护的行为。

怎样用 AI 生成测试而不是复制实现?

把公共契约、示例、约束和现有的测试约定交给模型,并要求它先提出用例、再写代码。GitHub 的测试教程指出,助手可以帮助编写单元测试和集成测试,但复杂场景需要更详细的策略,生成的测试套件仍需审查是否遗漏用例。[1]

弱的提示词是“为这个函数写单元测试”:模型可能照搬分支、断言私有调用,或重复实现中的同一误解。更强的请求会说明哪些行为重要、哪些接口公开、预期有哪些失败、什么必须保持兼容。

把四类产物分开:

对象回答的问题
需求承诺了什么行为?
Oracle怎样判断结果正确?
场景矩阵必须执行哪些不同的条件?
测试代码代码库如何执行这些检查?

如果 oracle 只是“与当前实现一致”,测试就不可能发现实现是错的。

步骤 1:冻结需求和可信基线

用可观察的语言写下需求。“对负数时长返回 InvalidDuration,且不改变有效的秒数或分钟数”是可测试的;“改进时长校验”则不是。

修改前记录当前的测试命令和结果。修 bug 时,保存能复现它的最小输入。基线本已有失败时先分类,免得把无关故障算到新测试头上。

代码不熟悉时,先用证据解释目标代码的相关路径。在弄清入口、依赖、状态和外部副作用之后,再开始生成测试。

从实现之外定义 oracle

好的 oracle 来源包括公共 API 契约、协议规范、schema、验收标准、此前批准的行为,或独立算出的结果。解析器的 oracle 可以是一张明确的输入/输出表;授权检查的 oracle 可以是一张策略矩阵;财务计算则可能需要另行复核的公式和示例。

遇到歧义时如实记录,而不是让 AI 决定产品行为。如果两种结果都说得通,缺少的是需要人来作的决定,而不是写测试的问题。

步骤 2:建立正常、边界和失败矩阵

先要用例名称和理由。每一行只改变一个有意义的条件,并写明预期的可观察结果。

至少使用以下类别:

  • 正常: 有代表性的有效输入和兼容的输出。
  • 边界: 空值、零、最小值、最大值、恰好等于阈值,以及超出一步。
  • 失败: 格式错误、未授权、依赖不可用、超时和被拒绝的操作。
  • 状态: 首次调用、重复调用、重复输入、部分已有状态,以及失败后重试。
  • 交互: 以正确数据调用依赖、拒绝时不调用,以及执行了清理。
  • 对抗: 相关时,注入形态的文本、超大输入、路径穿越或不可信文档中的指令。

不要机械地加类别。纯格式化函数也许不需要网络故障用例,队列消费者则更需要确认和重试行为,而非几十种字符串变体。

矩阵只是规划工具。在测试实际执行、并检查过其对故障的敏感度之前,它不代表任何覆盖。

不要用不同包装重复同一场景

走同一条分支的十个值,通常只是一个等价类,而不是十道独立的防线。要求模型说明每个用例能发现什么独特的风险;没有带来独特观察的行就删掉。

GitHub 的单元测试生成教程建议把代码和清晰的指令交给助手,再复核生成结果。[2] 加上场景矩阵这一步,你就能在语法让它看起来“已经完成”之前评估设计。

步骤 3:提供安全且最小的测试上下文

分享需求、公共接口、相关实现、附近已有测试、fixture 构建器和确切的测试命令。写明代码库关于命名、异步行为、临时文件、数据库隔离和清理的约定。

不要粘贴真实客户记录、生产数据库导出、secret、签名 URL 或私有凭证。构建保留结构和边界条件的合成 fixture;逼真的 fixture 不需要真实身份。

要求模型在第一轮中不要添加依赖、批量重新生成快照、削弱断言或修改生产代码。如果测试之所以难写,是因为设计耦合过紧,就把这一设计约束单独记录下来,而不是用一个庞大的 mock 掩盖它。

步骤 4:审查 fixture、mock 与断言

生成的测试常常看似合理,却几乎什么都没证明。按执行顺序审查测试。

Fixture 必须真的制造目标条件

确认类型、单位、时区、编码、默认值、ID 和关联关系。一个意外被规范化的 fixture,可能让边界测试根本到不了边界。在隐藏默认值可能改变含义的地方,让构建器保持显式。

Mock 不应替换被测行为

必要时 mock 缓慢或外部的边界,但不要 mock 你正想验证的逻辑本身。尽可能在公共边界上断言。如果 mock 了存储库,既要验证返回的行为,也要验证发给该存储库的关键请求。

过度规定调用顺序的断言,会让无害的重构失败;约束不足的 mock,则可能放过未授权或重复的副作用。选择能表达契约的交互,例如“校验失败后不发生写入”,而不是断言每一次私有辅助调用。

断言必须能区分合理但错误的输出

result is not None、“没有抛异常”或宽泛的快照,可能让许多错误输出都通过。对真正重要的字段、错误类型、状态转换和外部副作用下断言。对集合,要考虑顺序、重复、缺失的行和意外多出的行。

GitHub 的负责任使用材料提醒,AI 输出可能不准确、不完整或不符合意图,需要人工监督。[3] 测试文件与生成的生产代码值得同样的审视。

步骤 5:先跑基线,再加入候选测试

加入新文件之前,先运行最近的可信测试组。然后加入最小、连贯的一组候选测试,只运行这个范围。失败可能揭示真实缺陷、错误的 oracle、损坏的 fixture 或环境假设;不要马上改断言。

按以下顺序排查:

  1. 重读需求和预期观察。
  2. 检查 fixture,确认执行了预期的路径。
  3. 检查测试是否隔离且确定。
  4. 检查实现和保存的证据。
  5. 把含糊的产品行为交给负责人决定。

如果失败揭示了实现缺陷,就保留这个用例,并转到证据驱动的 AI 调试流程。不要改写测试,把 bug 描述成正确行为。

步骤 6:证明重要测试能够失败

一个通过的测试,可能从未到达相关分支,或只是断言了一个常量。对每个高价值用例,引入一个临时的受控故障:反转比较、跳过校验、返回错误的字段,或省略预期的副作用。测试应当因预期的原因失败。

立即撤除故障,确认测试再次通过,并检查 diff,确保没有残留改动。这种本地敏感度检查不能代替变异测试基础设施,但能抓出空断言和无关的 fixture。

谨慎对待快照。快照只证明与一份已批准的文件相等。审查语义上的变化,而不是因为生成的输出变了就接受大规模更新。

步骤 7:检查确定性、清理和套件适配

涉及时间、随机性、并发、区域设置、顺序或外部资源时,把候选测试运行不止一次。在契约允许时控制时钟和随机种子。使用临时目录和隔离的数据库状态,并确认成功和失败两种情况下都会清理。

分层扩大验证:

  1. 只运行新测试;
  2. 最近的模块或包测试;
  3. 对改动代码运行 lint、类型检查和静态分析;
  4. 相关的集成测试;
  5. 变更准备就绪时,运行更广泛的代码库门禁。

不要声称本地通过就证明了其他操作系统、生产配置、外部服务或部署也没问题。NIST SSDF 把测试和审查放在一组安全开发实践之中,而不是当作唯一的完成信号。[4]

步骤 8:把测试 patch 当作可维护代码审查

对测试代码同样使用 AI 生成代码 Review 清单。检查依赖变化、隐藏的网络访问、不安全的临时路径、真实凭证、过多的 fixture、缓慢的重试和过宽的权限。

问一问,未来的维护者能否快速回答三个问题:

  • 这个测试保护的是什么契约?
  • 什么样的改动应该让它失败?
  • 哪些准备细节是必要的,哪些只是偶然的?

优先使用能体现行为的名称和简短的 fixture 注释,而不是一份生成过程的文字记录。AI 对话结束之后,测试和需求仍应有用。

使用 AI 生成测试后应该记录什么?

记录需求、oracle、新增的用例、运行过的命令、使用过的受控故障,以及未经测试的环境。把新的失败与基线失败分开。列出所有 mock,并说明它们替代了哪个真实边界。

如果 AI 还提出了生产代码补丁,就把小范围 AI 编码流程作为单独的授权来处理。测试可以指导变更,但生成测试并不等于获准改变它们所描述的行为。

总结

  • 从可观察的需求和独立的 oracle 出发。
  • 设计正常、边界、失败、状态、交互以及相关的对抗用例。
  • 使用最小的合成 fixture,只 mock 真正的边界。
  • 审查断言能否区分正确输出和看似合理的错误输出。
  • 用受控故障证明重要测试会失败,再分层运行验证。

常见问题

AI 能自动生成完整测试套件吗?

它可以提出很多用例并快速写出代码,但是否完整取决于需求、系统风险、环境和人的判断。把生成的测试当作候选,而不是认证。

应从实现还是需求生成测试?

两者都用,但由需求定义什么是正确。实现有助于定位分支和依赖,但不应成为唯一的 oracle。

覆盖率越高越好吗?

覆盖率可以揭示未执行的代码,但不能证明断言有意义或需求正确。宁要一小组能区分对错的测试,也不要装饰性的覆盖率。

测试失败时可以让 AI 自动更新 snapshot 吗?

只有在审查了语义差异、并确认新输出是预期的之后才可以。批量更新快照可能把回归重新定义为预期,从而掩盖它。

应该 mock 多少?

为了隔离,mock 外部或代价高的边界,而不是被测的行为。让关键的输入和输出保持可见,并为重要的边界假设补充集成层面的证据。

生成测试全部通过能证明 bug 已修复吗?

不能。它们只证明该环境中被执行的场景。保留原始复现,审查补丁,运行相关的更广泛检查,并记录尚未验证的内容。

生成测试暴露需求歧义怎么办?

暂停,请产品、协议或政策负责人定义契约。不要让模型在几个合理的结果之间作选择,或把偶然的行为固定为永久行为。

测试文件可以包含生产数据吗?

不可以。使用合成的或经正式批准的匿名化 fixture,避免凭证、客户记录、私有 URL 和对线上服务的依赖。


延伸阅读:

免责声明:本文提供一般软件测试信息。安全、监管、质量和发布要求取决于你的系统与组织;高影响软件需要合格审阅和目标环境验证。

来源:

  1. GitHub Docs — Writing tests with GitHub Copilot — https://docs.github.com/en/copilot/tutorials/write-tests
  2. GitHub Docs — Generating unit tests — https://docs.github.com/en/copilot/tutorials/copilot-cookbook/testing-code/generate-unit-tests
  3. GitHub Docs — Application card for Copilot inline suggestions — https://docs.github.com/en/copilot/responsible-use/inline-suggestions
  4. NIST — Secure Software Development Framework — https://csrc.nist.gov/pubs/sp/800/218/final

Sources checked 2026 年 8 月 24 日。

开启 3 天免费试用

注册即可免费体验全部高级功能。

*仅限新用户;每位用户只能获得一次试用。

如何用 AI 生成测试:先定 oracle,再用受控故障验证 | AethoVPN