跳至正文
Agents — Agent 开发中的测试方法:Specification、Test Oracle 与验证证据

Agent 开发中的测试方法:Specification、Test Oracle 与验证证据

AI 参与说明(Agent:Codex):本文由 Codex 基于文中所列研究论文、测试工具官方文档与工程实践资料协助整理。概念分类和落地顺序是工程建议,不是新的测试标准。资料整理于 2026-09-07。

资料与修订记录

本次撰写与校验运行记录:模型 gpt-6-astra;reasoning effort:medium;执行入口:Codex Desktop;提供方:openai;CLI 版本:0.153.4(不代表桌面 App 版本)。参数来自本次任务运行记录。

英文表述修订(2026-09-07,Agent:Codex):核对原始论文与 Stryker 文档,调整测试方法定义、区分 Mutant 状态并统一 Agent 术语。此次运行记录:模型 gpt-6-astra,reasoning effort ultra,执行入口 Codex Desktop,提供方 openai,CLI 版本 0.153.4(不代表桌面 App 版本);上方 medium 记录仍归属于原有撰写与校验。

术语统一(2026-09-08,Agent:Codex):为 Specification、Test Oracle、Agent Evaluation 等概念固定英文名称,区分 Property、Mutant 与对应的测试方法;保留中文关系解释与既有参与记录。运行记录:模型 gpt-6-astra,reasoning effort ultra,执行入口 Codex Desktop,提供方 openai,CLI 版本 0.153.4(不代表桌面 App 版本)。

导读补充(2026-09-08,Agent:Codex):在正文前增加关键术语的中文名称与简要解释对照表,正文继续使用统一的英文名称。运行记录:模型 gpt-6-astra,reasoning effort ultra,执行入口 Codex Desktop,提供方 openai,CLI 版本 0.153.4(不代表桌面 App 版本)。

阅读前先看这几个词

先对照中文名称,再用简要解释理解开篇的关键术语;正文统一使用英文名称,具体边界和例子在后续小节展开。

英文术语中文名称简要解释
Specification规格说明对预期行为、规则及适用范围的明确描述。
Test Oracle测试预言机判断观察结果是否符合预期的依据或机制。
Test Double测试替身测试中用来替代真实依赖的对象。
Mock模拟对象用于检查依赖是否按预期被调用的测试对象。
Unit Testing单元测试围绕一个较小代码单元检查行为,例如一个函数或类。
Integration Testing集成测试检查多个模块或服务连接后能否正确协作。
End-to-end Testing端到端测试沿完整业务流程检查系统各部分协作后的结果。
LLM-as-a-judge大语言模型作为裁判让大语言模型依据明确标准,对结果或执行过程给出评价。
Side Effect副作用除了返回结果,还改变外部可观察状态的行为,例如写入记录或发送消息。
Test Case测试用例为验证某个行为而安排的输入、执行条件与预期结果。
Assertion断言检查某个条件是否成立,并在不成立时报告失败的语句。
Deterministic Assertion确定性断言按固定规则直接检查结果,相同的检查输入与规则产生相同判断。

核心观点:从“多写测试”转向“获得什么证据”

Agent 可以同时生成实现、测试和 Test Double(用于替代真实依赖,例如 Mock)。如果它们共享同一个错误假设,测试可能全部通过,却不能满足业务要求。因而需要分别检查三个问题:预期结果依据什么制定,测试有没有机会发现实现错误,以及失败能否稳定复现。

这些需求大多对应已有的测试思想。Agent 开发改变的是使用它们的成本与必要性,并没有让 Unit Testing、Integration Testing 或 End-to-end Testing 失去价值。也不必把每个功能都交给 LLM-as-a-judge:明确的数值、状态、权限与 Side Effect,通常可以用 Deterministic Assertion 检查。

概念地图:这些名称不在同一个维度

维度代表概念要回答的问题
验证对象与范围Unit Testing、Component Testing、Integration Testing、Contract Testing、End-to-end Testing测的是哪一段真实系统,哪些依赖被替换?
Specification 与工作方式Acceptance Criteria、Executable Specification、Test-driven Development、Behavior-driven Development预期行为从哪里来,怎样约束实现?
输入与反例生成Example-based Testing、Property-based Testing、Metamorphic Testing、Model-based Testing、Fuzz Testing除了想到的几个例子,还能怎样寻找错误?
对照与判定依据Test Oracle、Reference Model、Differential Testing、Characterization Testing、Snapshot Testing、Approval Testing根据什么说结果对、错或发生了变化?
测试质量与运行环境Mutation Testing、Code Coverage、Hermetic Testing、Deterministic Replay测试是否敏感、隔离、可复现?
非功能属性与约束Performance Testing、Security Testing、Accessibility Testing、Architecture Testing功能之外的成本、权限、体验和结构是否符合要求?
Agent 自身Agent EvaluationAgent 是否完成任务、遵守约束,以及结果是否可靠?

例如,同一个 Test Case 可以在 Unit Testing 中采用 Property-based Testing 生成输入,用 Reference Model 判定结果,再用 Mutation Testing 检查 Assertion 是否足够敏感。这些术语可以组合,不是一条只能选一个位置的测试层级。

Test Oracle:谁定义“正确”

Test Oracle 是判断观察结果是否符合预期的依据或机制。它可以是明确的期望值、必须始终满足的业务规则、独立的 Reference Implementation、已审阅的 Baseline,也可以来自人工判断或 LLM-as-a-judge。

假设需求是“未登录时禁止提交”,Agent 却理解成“离线时一律入队”。若测试也从后者推导期望值,实现和测试就会一起出错。独立性首先来自 Specification(对预期行为与约束的明确描述)、证据和变更控制,而不是简单地换另一个 Agent 写测试。

实践中应保留可追踪的需求 ID,把关键期望值连接到业务规则。Coding Agent 可以提出 Specification 变更,但不能通过删除 Assertion、修改 Baseline 或跳过失败场景来自动宣告修复。采用 LLM-as-a-judge 时,要记录 Rubric、模型配置、实际输入证据,并用人工标注的正反例校准。

用业务要求约束开发过程

Acceptance Testing 从可观察的业务结果验收功能,例如“同一个草稿重复提交,不产生两条业务记录”。Executable Specification 将 Specification 与可以运行的检查连接起来;Behavior-driven Development 强调围绕行为与例子建立共同理解,不能仅等同于一种 Given / When / Then 文件格式。

Test-driven Development 是开发工作方式:先写一个体现目标行为的失败测试,再实现让它通过,最后重构并保留测试。它不规定必须使用哪种测试范围。让 Agent 执行这套循环时,要先确认测试因为预期行为尚未实现而失败,而不是因为导入错误或环境损坏而失败。Test-driven Development

它们适合减少需求歧义,但不能保证 Specification 本身正确。对于小范围、低风险修改,不必机械地增加测试;应优先覆盖会造成真实损失的错误模式。

Property-based Testing:验证规则,而不只枚举例子

Example-based Testing 指定几个输入与结果;Property-based Testing 定义输入空间和应成立的 Property(测试期望成立的关系或约束),由工具生成多组输入,寻找反例。部分工具还能通过 Shrinking 缩小失败输入,便于诊断。Hypothesis 官方介绍

对跨端业务逻辑,可以检查:

  • 在约定的非负金额域中,最终价格不应为负。
  • 对同一个业务操作 ID 的重复请求,不应重复产生业务记录。
  • 排序后的元素多重集合与原输入相同,且顺序符合比较规则。

Property 必须说明适用域。例如,“增加商品不会降低总价”在有满减优惠时未必成立;“编码再解码得到原值”需要排除不支持的数据类型,并定义相等语义。错误的 Property 会把正确实现判为失败。

对 Agent 的任务可以从“多补几个测试”改成“列出关键 Property、适用范围和反例生成策略”。测试通过仍只是已执行输入的证据,不是对所有输入的形式化证明。

Metamorphic Testing:没有现成答案,也可以检查结果之间的关系

Metamorphic Testing checks specified relationships between the outputs of related test inputs. These relationships help construct follow-up tests when individual expected outputs are difficult to determine. Metamorphic Testing 原始技术报告

例如,在明确不考虑顺序相关优惠、舍入和溢出的整数加总模型中,调换项目顺序不应改变总和。对于只做确定性排序的函数,重新排列输入后,排序结果应一致。

这类关系也可以由 Property-based Testing 生成输入来检查,两者并不互斥。关系仍需经过审阅:搜索排序可能受个性化影响,图像变换可能改变识别目标,不能直接套用“输入变化、结果不变”。

Mutation Testing:故意改坏实现,看测试会不会失败

Mutation Testing introduces small changes into a program and reruns its tests. A Mutant is killed when a test fails because of the change. Stryker:What is mutation testing?

例如,把 > 改成 >=、删除分支或替换返回值,再检查现有测试能否识别变化。Code Coverage 只说明哪些代码被执行过;这些定向改动则帮助检查 Assertion 是否足够敏感。

对 Agent 生成的权限、边界比较和错误处理测试,这是一种有价值的质量检查。它直接追问:如果实现犯一个常见错误,测试能否阻止它?

Mutant 指施加小改动后的程序版本。Survived 表示测试仍通过,但不一定代表漏测:该 Mutant 也可能与原程序在适用域内等价。CompileError 和 Timeout 是另外的状态,不能归入 Survived;在 Stryker 的统计中,Timeout 计入已检测的 Mutant,CompileError 不计入有效的 Mutant。应区分状态并报告选择范围,不能把一个高 Mutation Score 当成系统正确率。优先检查关键模块或当前变更,控制计算成本。Mutant states and metrics

Model-based Testing 与 Stateful Testing:验证操作顺序

许多 Bug 不在单个函数调用中,而在“先做什么、再做什么”。Model-based Testing 用简化的 Reference Model 描述允许的状态与动作;Stateful Testing 保留动作之间的状态联系。工具可以生成动作序列,并把真实实现与 Reference Model 比较。Hypothesis:Stateful Testing 文档

例如,离线草稿可能经历:编辑、提交、超时、重试、退出账号、重新启动。每一步都应满足账号隔离、状态合法性与记录唯一性等要求。只验证“点击一次提交成功”,无法覆盖这些组合。

这里的 Reference Model 描述业务状态,不是 LLM。它应比实现简单,避免复制实现的同一错误。只按顺序执行操作的 Model-based Testing 也不自动证明并发正确;并发操作还需要调度控制、历史记录以及明确的一致性判据。

用现有系统作为对照

这几种方法适合 Agent 重构、迁移或跨端重写,但其证据含义不同:

方法对照方式关键限制
Differential Testing同一输入交给 iOS / Android、新旧实现或多个独立实现一致可能是一起错;不同也可能是允许的平台差异
Characterization Testing先记录既有实现的实际行为,作为重构保护记录的是“现在怎样”,不能自动升级为“业务应当怎样”
Snapshot Testing 与 Approval Testing输出与已审阅的 Snapshot 或 Baseline 比较Baseline 质量决定意义;自动接受所有变化会削弱保护

应明确哪些历史行为必须保持,哪些是准备修复的缺陷,并把后者改成符合 Specification 的期望。对输出做归一化时,只排除真正无关的随机 ID 或格式差异,不能顺手忽略业务时间、权限字段或错误码。

既有系统难以直接测试时,可以先寻找可替换依赖、可观察结果的边界,再建立保护性测试。Legacy Seam

Fuzz Testing 与 Fault Injection:分别扰动输入和环境

Fuzz Testing 大量生成输入或修改已有输入,寻找崩溃、挂起、Assertion 失败或其他异常。它适合解析器、文件导入、协议和不可信输入边界;不是只能做纯随机字符串测试,也不能只靠“没有崩溃”判断业务正确。OSS-Fuzz

Fault Injection 则改变依赖或执行环境:超时、断连、失败响应、重复消息,或在明确时点中断进程。对同步、上传和重试功能,可以在测试环境注入“服务端已经成功,客户端丢失响应”,再验证恢复与幂等行为。

Chaos Engineering 通常进一步关注系统在受控扰动下的韧性假设、稳态指标和影响范围;并非每个超时 Mock 都需要称为 Chaos Engineering。日常 Agent 开发优先在隔离环境做可复现的 Fault Injection,按风险决定是否需要更大范围实验。Principles of Chaos Engineering

Hermetic Testing 与 Deterministic Replay:给 Agent 稳定的反馈

Hermetic Testing 强调隔离或控制外部依赖;Deterministic Replay 强调记录并重放影响执行的输入、事件和调度条件。两者都服务于稳定诊断,但并非同义词。

应显式控制时间、随机数、数据初始状态、外部响应和资源清理。遇到不稳定测试,先定位原因;让 Agent 不断重跑直到一次成功,只会隐藏问题。重试可以帮助识别波动,但不能代替修复。Eradicating Non-Determinism in Tests

固定 Random Seed 也不保证一切可复现:网络、线程调度、文件系统状态和外部服务变化可能仍然影响结果。对生成式测试,应保存失败输入或事件序列,以及相关环境版本。

Architecture Testing 与非功能测试

Architecture Testing 把允许的模块依赖和结构规则变成可检查的约束,例如“业务层不依赖 UI 框架”“客户端模块不导入服务端实现”。它适合限制 Agent 在快速补功能时引入的依赖混乱,但不能判断架构本身是否适合业务。ArchUnit User Guide

功能通过也不代表性能、权限、可访问性或资源成本合格。按实际风险增加明确检查:越权访问是否被真实服务端拒绝,大文件处理是否超过资源预算,关键任务能否用辅助技术完成。阈值需要说明环境、样本和测量方式,不能用一次耗时结果宣称性能稳定。

Agent Evaluation:测试的是 Coding Agent 本身

当目标是比较模型、Prompt、工具配置或 Harness 时,被测对象是 Agent 的工作过程与交付结果。一个任务的最终测试通过,不等于 Agent 在一批任务上可靠。

可同时观察任务完成率、代码或环境的实际最终状态、工具轨迹中的约束违规、成本和耗时。不要要求所有成功运行必须走完全相同的工具序列;只约束有业务意义的顺序或禁止行为。评估需要多次 Trial 与清楚的统计口径,不能把“多试几次至少成功一次”当作每次运行的成功率。Demystifying evals for AI agents

开发反馈使用的 Test Case 与独立评估任务应适当分离,避免反复针对同一套题调优后高估泛化能力。评估标准和隐藏 Test Case 的修改权限也应独立管理。Deterministic Assertion、LLM-as-a-judge 与人工检查可以组合,但每种证据的范围应分别报告。

最小可运行例子:用 Property 识别 Mutant

下面用 Python 3.9+ 标准库演示一个排序 Specification:输出既要有序,也要保留输入元素的出现次数。将代码保存为 test_sort_properties.py,运行 python3 test_sort_properties.py。输入是整数,示例不涉及自定义比较器、浮点 NaN 或对象稳定排序。

这不是完整的 Property-based Testing 工具或自动 Mutation Testing 框架:它显式枚举有限输入,并手动注入一个错误,用来说明两种思路的关系。

python
from collections import Counter
from itertools import product


def ordered(values):
    return all(a <= b for a, b in zip(values, values[1:]))


def verify(sort_fn):
    count = 0
    for size in range(5):
        for values in product((-1, 0, 1), repeat=size):
            result = sort_fn(values)
            if not ordered(result):
                raise AssertionError(f"Not ordered: {values} -> {result}")
            if Counter(result) != Counter(values):
                raise AssertionError(f"Elements changed: {values} -> {result}")
            count += 1
    return count


def wrong_sort(values):
    return sorted(set(values))  # Deliberate mutation: loses duplicates.


if __name__ == "__main__":
    print(f"Correct implementation: {verify(sorted)} cases passed")
    try:
        verify(wrong_sort)
    except AssertionError as error:
        print(f"Mutation detected: {error}")
    else:
        raise AssertionError("Mutation survived")

预期输出:

text
Correct implementation: 121 cases passed
Mutation detected: Elements changed: (-1, -1) -> [-1]

只检查“输出有序”,错误实现也会通过;补充“元素及次数保持不变”,才会识别这个 Mutant。这个例子说明 Property 需要足够完整,而不是证明排序函数对所有输入都正确。

如何用于日常 Agent 任务

不必每次启用所有方法。先根据失败风险选择证据,再决定工具和成本:

改动优先方法希望获得的证据
修复明确 BugRegression Testing,必要时按 Test-driven Development 推进未修复版本因目标缺陷失败,修复后通过
iOS / Android 重写同一业务功能共享 Specification、Property-based Testing、Differential Testing各端分别符合 Specification;允许差异有明确说明
离线同步与重试Stateful Testing 与 Model-based Testing、Fault Injection错误顺序与恢复场景下状态和Side Effect仍正确
大规模重构Characterization Testing、Architecture Testing、关键 End-to-end Testing约定保留的行为不变,依赖边界与关键路径成立
解析或导入不可信文件Fuzz Testing、资源限制与明确的错误 Assertion异常输入被处理,失败可重现
怀疑 Agent 测试过弱定向 Mutation Testing常见错误能被现有测试识别
更换编码模型或工具链独立 Agent Evaluation、多次运行按统一任务和口径比较结果、约束与成本

对所有场景,共同要求是:说明替换了哪些依赖,保留实际执行证据,检查测试是否真的被发现和运行,并报告尚未覆盖的风险。自动生成的测试数量与 Code Coverage 可以辅助观察,不能代替这些判断。

关联阅读

本文共 4824 字,创建于 Sep 7, 2026

相关标签:Tools, ByAI

博客助手

正在打开博客助手…