AI 参与说明(Agent:Codex):本文由 Codex 基于文中所列研究论文、测试工具官方文档与工程实践资料协助整理。概念分类和落地顺序是工程建议,不是新的测试标准。资料整理于 2026-09-07。
本次撰写与校验运行记录:模型
gpt-6-astra;reasoning effort:medium;执行入口:Codex Desktop;提供方:openai;CLI 版本:0.153.4(不代表桌面 App 版本)。参数来自本次任务运行记录。
核心观点:从“多写测试”转向“获得什么证据”#
Agent 可以同时生成实现、测试和 Mock。如果它们共享同一个错误假设,测试可能全部通过,却不能满足业务要求。因而需要分别检查三个问题:预期结果依据什么制定,测试有没有机会发现实现错误,以及失败能否稳定复现。
这些需求大多对应已有的测试思想。Agent 开发改变的是使用它们的成本与必要性,并没有让 Unit Test、Integration Test 或 E2E Test 失去价值。也不必把每个功能都交给 LLM Judge:明确的数值、状态、权限与副作用,通常可以直接断言。
概念地图:这些名称不在同一个维度#
| 维度 | 代表概念 | 要回答的问题 |
|---|---|---|
| 验证对象与范围 | Unit、Component、Integration、Contract、E2E | 测的是哪一段真实系统,哪些依赖被替换? |
| 规格与工作方式 | Acceptance Criteria、Executable Specification、TDD、BDD | 预期行为从哪里来,怎样约束实现? |
| 输入与反例生成 | Example-based、Property-based、Metamorphic、Model-based、Fuzz Testing | 除了想到的几个例子,还能怎样寻找错误? |
| 对照与判定依据 | Test Oracle、Reference Model、Differential、Characterization、Snapshot / Approval | 根据什么说结果对、错或发生了变化? |
| 测试质量与运行环境 | Mutation Testing、Coverage、Hermetic Testing、Deterministic Replay | 测试是否敏感、隔离、可复现? |
| 非功能属性与约束 | Performance、Security、Accessibility、Architecture Tests | 功能之外的成本、权限、体验和结构是否符合要求? |
| Agent 自身 | Task Evals、Trajectory Checks、Outcome Checks | Agent 是否完成任务、遵守约束,以及结果是否可靠? |
例如,同一个测试可以在 Unit 范围运行,采用 Property-based 生成输入,用参考模型判定结果,再用 Mutation Testing 检查断言是否足够敏感。这些术语可以组合,不是一条只能选一个位置的测试层级。
Test Oracle:谁定义“正确”#
Test Oracle 是判断观察结果是否符合预期的依据或机制。它可以是明确的期望值、业务不变量、独立参考实现、已审阅的基线,也可以是人工或模型评分。
假设需求是“未登录时禁止提交”,Agent 却理解成“离线时一律入队”。若测试也从后者推导期望值,实现和测试就会一起出错。独立性首先来自规格、证据和变更控制,而不是简单地换另一个 Agent 写测试。
实践中应保留可追踪的需求 ID,把关键期望值连接到业务规则。编码 Agent 可以提出规格变更,但不能通过删除断言、修改基线或跳过失败场景来自动宣告修复。模型评分则要记录评分规则、模型配置、实际输入证据,并用人工标注的正反例校准。
Acceptance Testing、Executable Specification 与 TDD#
Acceptance Testing 从可观察的业务结果验收功能,例如“同一个草稿重复提交,不产生两条业务记录”。Executable Specification 把规格连接到可以运行的检查;BDD 强调围绕行为与例子建立共同理解,不能仅等同于一种 Given / When / Then 文件格式。
TDD 是开发工作方式:先写一个体现目标行为的失败测试,再实现让它通过,最后重构并保留测试。它不规定必须使用哪种测试范围。让 Agent 执行这套循环时,要先确认测试因为预期行为尚未实现而失败,而不是因为导入错误或环境损坏而失败。Test Driven Development
它们适合减少需求歧义,但不能保证规格本身正确。对于小范围、低风险修改,不必机械地增加测试;应优先覆盖会造成真实损失的错误模式。
Property-based Testing:验证规则,而不只枚举例子#
Example-based Testing 指定几个输入与结果;Property-based Testing 定义输入空间和应成立的性质,由工具生成多组输入,寻找反例。部分工具还能通过 Shrinking 缩小失败输入,便于诊断。Hypothesis 官方介绍
对跨端业务逻辑,可以检查:
- 在约定的非负金额域中,最终价格不应为负。
- 对同一个业务操作 ID 的重复请求,不应重复产生业务记录。
- 排序后的元素多重集合与原输入相同,且顺序符合比较规则。
性质必须说明适用域。例如,“增加商品不会降低总价”在有满减优惠时未必成立;“编码再解码得到原值”需要排除不支持的数据类型,并定义相等语义。错误的性质会把正确实现判为失败。
对 Agent 的任务可以从“多补几个测试”改成“列出关键不变量、适用范围和反例生成策略”。测试通过仍只是已执行输入的证据,不是对所有输入的形式化证明。
Metamorphic Testing:没有现成答案,也可以检查结果之间的关系#
有时很难为每个输入手算正确结果,但可以预测改变输入后,输出应满足什么关系。Metamorphic Testing 用这种关系构造后续测试。Metamorphic Testing 原始技术报告
例如,在明确不考虑顺序相关优惠、舍入和溢出的整数加总模型中,调换项目顺序不应改变总和。对于只做确定性排序的函数,重新排列输入后,排序结果应一致。
这类关系也可以由 Property-based Test 生成输入来检查,两者并不互斥。关系仍需经过审阅:搜索排序可能受个性化影响,图像变换可能改变识别目标,不能直接套用“输入变化、结果不变”。
Mutation Testing:故意改坏实现,看测试会不会失败#
Code Coverage 表示哪些代码被执行过,不能单独说明结果被认真断言。Mutation Testing 对程序施加小变化,例如把 > 改成 >=、删除分支或替换返回值,再运行测试。如果测试失败,就说明它识别了这次变化。Stryker:What is mutation testing?
对 Agent 生成的权限、边界比较和错误处理测试,这是一种有价值的质量检查。它直接追问:如果实现犯一个常见错误,测试能否阻止它?
存活的变异不一定都代表漏测:有些与原程序在适用域内等价,有些无法编译,有些超时。应区分状态并报告选择范围,不能把一个高 Mutation Score 当成系统正确率。优先检查关键模块或当前变更,控制计算成本。Mutant states and metrics
Model-based / Stateful Testing:验证操作顺序#
许多 Bug 不在单个函数调用中,而在“先做什么、再做什么”。Model-based Testing 用简化模型描述允许的状态与动作;Stateful Testing 保留动作之间的状态联系。工具可以生成动作序列,并把真实实现与模型比较。Hypothesis Stateful tests
例如,离线草稿可能经历:编辑、提交、超时、重试、退出账号、重新启动。每一步都应满足账号隔离、状态合法性与记录唯一性等要求。只验证“点击一次提交成功”,无法覆盖这些组合。
模型中的 Model 通常指业务状态模型,不是 LLM。它应比实现简单,避免复制实现的同一错误。普通顺序模型测试也不自动证明并发正确;并发操作还需要调度控制、历史记录以及明确的一致性判据。
Differential、Characterization 与 Approval:把现有系统作为对照#
这几种方法适合 Agent 重构、迁移或跨端重写,但其证据含义不同:
| 方法 | 对照方式 | 关键限制 |
|---|---|---|
| Differential Testing | 同一输入交给 iOS / Android、新旧实现或多个独立实现 | 一致可能是一起错;不同也可能是允许的平台差异 |
| Characterization Testing | 先记录既有实现的实际行为,作为重构保护 | 记录的是“现在怎样”,不能自动升级为“业务应当怎样” |
| Snapshot / Approval Testing | 输出与已审阅的快照或基线比较 | 基线质量决定意义;自动接受所有变化会削弱保护 |
应明确哪些历史行为必须保持,哪些是准备修复的缺陷,并把后者改成符合规格的期望。对输出做归一化时,只排除真正无关的随机 ID 或格式差异,不能顺手忽略业务时间、权限字段或错误码。
既有系统难以直接测试时,可以先寻找可替换依赖、可观察结果的边界,再建立保护性测试。Legacy Seam
Fuzz Testing 与 Fault Injection:分别扰动输入和环境#
Fuzz Testing 大量生成或变异输入,寻找崩溃、挂起、断言失败或其他异常。它适合解析器、文件导入、协议和不可信输入边界;不是只能做纯随机字符串测试,也不能只靠“没有崩溃”判断业务正确。OSS-Fuzz
Fault Injection 则改变依赖或执行环境:超时、断连、失败响应、重复消息,或在明确时点中断进程。对同步、上传和重试功能,可以在测试环境注入“服务端已经成功,客户端丢失响应”,再验证恢复与幂等行为。
Chaos Engineering 通常进一步关注系统在受控扰动下的韧性假设、稳态指标和影响范围;并非每个超时 Mock 都需要称为混沌工程。日常 Agent 开发优先在隔离环境做可复现故障注入,按风险决定是否需要更大范围实验。Principles of Chaos Engineering
Hermetic Testing 与 Deterministic Replay:给 Agent 稳定的反馈#
Hermetic Testing 强调隔离或控制外部依赖;Deterministic Replay 强调记录并重放影响执行的输入、事件和调度条件。两者都服务于稳定诊断,但并非同义词。
应显式控制时间、随机数、数据初始状态、外部响应和资源清理。遇到不稳定测试,先定位原因;让 Agent 不断重跑直到一次成功,只会隐藏问题。重试可以帮助识别波动,但不能代替修复。Eradicating Non-Determinism in Tests
固定 Random Seed 也不保证一切可复现:网络、线程调度、文件系统状态和外部服务变化可能仍然影响结果。对生成式测试,应保存失败输入或事件序列,以及相关环境版本。
Architecture Tests 与非功能测试#
Architecture Tests 把允许的模块依赖和结构规则变成可检查的约束,例如“业务层不依赖 UI 框架”“客户端模块不导入服务端实现”。它们适合限制 Agent 在快速补功能时引入的依赖混乱,但不能判断架构本身是否适合业务。ArchUnit User Guide
功能通过也不代表性能、权限、可访问性或资源成本合格。按实际风险增加明确检查:越权访问是否被真实服务端拒绝,大文件处理是否超过资源预算,关键任务能否用辅助技术完成。阈值需要说明环境、样本和测量方式,不能用一次耗时结果宣称性能稳定。
Agent Evals:测试的是开发者代理本身#
当目标是比较模型、Prompt、工具配置或 Harness 时,被测对象是 Agent 的工作过程与交付结果。一个任务的最终测试通过,不等于代理在一批任务上可靠。
可同时观察任务完成率、代码或环境的实际最终状态、工具轨迹中的约束违规、成本和耗时。不要要求所有成功运行必须走完全相同的工具序列;只约束有业务意义的顺序或禁止行为。评估需要多次 Trial 与清楚的统计口径,不能把“多试几次至少成功一次”当作每次运行的成功率。Demystifying evals for AI agents
开发反馈用例与独立评估任务应适当分离,避免反复针对同一套题调优后高估泛化能力。评估标准和隐藏用例的修改权限也应独立管理。确定性判定、模型评分与人工检查可以组合,但每种证据的范围应分别报告。
最小可运行例子:性质与变异如何互补#
下面用 Python 3.9+ 标准库演示一个排序规格:输出既要有序,也要保留输入元素的出现次数。将代码保存为 test_sort_properties.py,运行 python3 test_sort_properties.py。输入是整数,示例不涉及自定义比较器、浮点 NaN 或对象稳定排序。
这不是完整的 Property-based 工具或自动变异框架:它显式枚举有限输入,并手动注入一个错误,用来说明两种思路的关系。
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")预期输出:
Correct implementation: 121 cases passed
Mutation detected: Elements changed: (-1, -1) -> [-1]只检查“输出有序”,错误实现也会通过;补充“元素及次数保持不变”,才会抓住这一变异。这个例子说明测试性质需要足够完整,而不是证明排序函数对所有输入都正确。
如何用于日常 Agent 任务#
不必每次启用所有方法。先根据失败风险选择证据,再决定工具和成本:
| 改动 | 优先方法 | 希望获得的证据 |
|---|---|---|
| 修复明确 Bug | Regression Test,必要时按 TDD 推进 | 未修复版本因目标缺陷失败,修复后通过 |
| iOS / Android 重写同一业务功能 | 共享规格、Property-based、Differential | 各端分别符合规格;允许差异有明确说明 |
| 离线同步与重试 | Stateful / Model-based、Fault Injection | 错误顺序与恢复场景下状态和副作用仍正确 |
| 大规模重构 | Characterization、Architecture、关键 E2E | 约定保留的行为不变,依赖边界与关键路径成立 |
| 解析或导入不可信文件 | Fuzz、资源限制与明确错误断言 | 异常输入被处理,失败可重现 |
| 怀疑 Agent 测试过弱 | 定向 Mutation Testing | 常见错误能被现有测试识别 |
| 更换编码模型或工具链 | 独立 Task Evals、多次运行 | 按统一任务和口径比较结果、约束与成本 |
对所有场景,共同要求是:说明替换了哪些依赖,保留实际执行证据,检查测试是否真的被发现和运行,并报告尚未覆盖的风险。自动生成的测试数量与 Coverage 可以辅助观察,不能代替这些判断。
关联阅读#
- 契约测试:接口兼容、多端功能一致性与 AI Agent 编码:接口与多端业务规格的具体落地方案。
- 前端 UI 测试全景:浏览器行为、视觉和可访问性验证。
- Agent 工程实践导航。