Engineering note
测试 Agent,而不是只测试方法
这是「Agent 工程实战」的第 15 篇。专题从一个能聊天、能调工具的 .NET Agent 出发,逐步补齐可靠性、安全、测试与发布能力。
本篇要解决的问题: 用可重复任务、假模型和工具轨迹来验证 Agent 行为,而非只看最终回答。
本章定位:本章讨论如何测试具有不确定性和外部依赖的 Agent。你会用 fake model、fake tool 和本地 SSE 服务测试行为轨迹,而不是只看最终文本。
建议阅读方式:先通读原理,再对照当前项目源码,最后完成本章实践。测试的目标是保护 Agent 的决策边界,让你敢于更换模型、提示词和工具实现。
本章导读
本章讨论如何测试具有不确定性和外部依赖的 Agent。你会用 fake model、fake tool 和本地 SSE 服务测试行为轨迹,而不是只看最终文本。
本章采用“源码观察 → 概念拆解 → 工程改造 → 实践验证”的顺序。示例中的接口和代码骨架用于说明设计方向,真正提交代码时应结合项目当前状态逐步落地。
15.1 为什么 Agent 测试更难
模型输出具有不确定性,直接调用真实 API 会带来成本、速度和稳定性问题。因此要把模型客户端抽象出来,用 fake response 驱动 Agent Loop。
15.2 最小 fake model
public sealed class FakeModelClient : IModelClient
{
public Queue<ModelEvent> Events { get; } = new();
public async IAsyncEnumerable<ModelEvent> StreamAsync(
ModelRequest request,
[System.Runtime.CompilerServices.EnumeratorCancellation]
CancellationToken cancellationToken = default)
{
while (Events.TryDequeue(out var item))
{
cancellationToken.ThrowIfCancellationRequested();
yield return item;
await Task.Yield();
}
}
}
15.3 必须覆盖的场景
- 纯文本回答;
- 一次工具调用后回答;
- 多次工具调用;
- 无效 JSON 参数;
- 缺少参数;
- 工具抛异常;
- 模型连续调用工具;
- HTTP 429 和 5xx;
- 用户取消;
- 工具超时;
- 输出包含敏感数据时的脱敏。
15.4 端到端测试
端到端测试可以用本地 fake HTTP server 验证 SSE 解析。真实模型评测则放到单独的评测集,不要让每次普通单元测试都访问线上模型。
15.5 本章交付物
Agent.Tests测试项目;- 工具单元测试;
- SSE 解析测试;
- Agent Loop 集成测试;
- 一组固定的回归用例。
15.6 测试金字塔
测试可以分三层:
- 纯单元测试:路径解析、参数转换、SSE 解析、工具策略;
- 组件测试:Agent Loop + fake model + fake tools;
- 端到端测试:本地 HTTP server 或少量真实模型任务。
越靠近真实服务,成本和不稳定性越高,因此数量应越少。不要用一组昂贵的真实模型测试去替代几十个确定性的单元测试。
15.7 测试工具调用轨迹
最终文本不是唯一断言。更重要的是轨迹:
Assert.Collection(executor.Calls,
call => Assert.Equal("filesystem.read_file", call.Name),
call => Assert.Equal("knowledge.search", call.Name));
还应断言禁止调用的工具没有被调用,参数被正确校验,工具失败后是否按策略重试。
15.8 记录回归样例
每次发现 bug,都把用户输入、fake model 响应和预期轨迹保存成回归用例。不要只修代码不留样例,否则下一次重构很可能重新引入同一问题。
样例中不要包含真实密钥、私人文件和不可公开的业务数据。可以把敏感内容替换成结构相同的假数据。
15.9 属性测试与模糊测试
解析器和路径解析器很适合做边界测试。例如生成随机的 SSE 空行、截断 JSON 和特殊路径,验证程序不会崩溃、不会访问 workspace 外资源。
在引入模糊测试前,先定义不变量:非法路径永远不能解析成功,解析器永远不能把任意文本当成成功工具调用。
15.10 练习:先写测试再修循环
先写一个失败测试:fake model 第一次返回最终文本,期望模型客户端只被调用一次;再写一个工具调用测试,期望第二次请求携带 tool message。最后才修改 Agent Loop。这个过程能让测试成为设计工具,而不只是验收工具。
15.11 本章产出
测试的最终目标不是追求覆盖率数字,而是让你敢于替换模型客户端、注册中心和工具执行器,同时知道 Agent 的行为没有悄悄改变。
下一阶段:让 Agent 进入真实工作流。 后续文章处理检索、计划、服务化、审批与发布。
单篇实战作业
实践:先写一个断言工具调用轨迹的失败测试,再修改 Agent Loop,体会测试如何反向塑造接口。
建议把作业拆成一个独立提交,并在提交说明中写清楚:改动前的行为、改动后的行为、验证命令、尚未解决的风险。测试的目标是保护 Agent 的决策边界,让你敢于更换模型、提示词和工具实现。
章节复盘
复盘问题:测试失败时,失败的是模型随机性还是系统契约?把随机部分替换为可重复的 fake。
本章的完成标准不是把所有设计一次性做完,而是能把它变成项目中的一个明确边界,并为下一章留下可验证的接口。
下一篇:第 16 篇《知识库与检索增强:先让答案可追溯》
如果你正在把 Agent 放进真实工作流,建议完成本篇的实战作业后再继续:每一步都应留下可验证的代码、测试或运行记录。