AIAgent自动化测试与CI/CD集成:一份来自工程一线的实战手册
最近这半年,我一直在做某AI Agent平台的自动化测试体系,从零搭到生产环境,踩的坑比过去三年的传统测试还要多。之所以难,核心就一句话:你没法像断言一个HTTP接口那样断言一个Agent的输出。同样是“帮我查一下上季度销售数据”,它可能调CRM、可能调数据库、可能在几条SQL之间纠结,最后给你的答案大差不差但绝不逐字相同。这种不确定性,直接颠覆了我们对“测试”这件事的基本假设。
这篇东西,是我把整套方案从设计到落地的完整记录,包含测试分层策略、评估体系建设、CI/CD流水线编排、质量门禁设计,以及一堆只有真跑过才会懂的坑。适合正在把Agent从Demo推向生产的后端开发、测试开发、平台工程师和架构师。如果你手头的Agent还停留在“跑起来再说”的阶段,这篇文章应该能帮你少走至少半年的弯路。
1. AI Agent测试到底难在哪
1.1 传统测试三板斧为什么失灵
传统软件测试有三板斧:固定断言、Mock外部依赖、管理状态。接口测试断言响应中的字段,服务测试Mock数据库和第三方,前端测试锁定DOM结构。这些方法有一个共同前提——程序的输出是确定性的,输入一样,输出就应该一模一样。
Agent架构把这个前提打碎了。它由LLM驱动,内部经过意图识别、任务规划、工具选择、工具执行、结果总结等一系列环节。LLM本身是概率模型,温度不为零时输出天然有浮动;即便温度调成0,不同模型版本、不同上下文写法,输出仍然可能漂移。更麻烦的是,Agent调用外部工具(查询数据库、调用API、操作文件),执行顺序和参数都依赖模型当时的决策,这导致传统的Mock方式很难下手——你连“它下一步会调用哪个工具”都预测不了,又怎么去Mock呢?
实际测试中还遇到一个更上头的问题:Agent的错误往往不是“返回值不对”,而是“行为路径不对”。比如用户问“帮我删除这个文件”,Agent正确理解了意图,也调用了删除工具,但参数传成了绝对路径而不是相对路径,文件没删掉但也没报错。传统断言根本抓不到这种问题,因为它只看最终结果有没有成功,不会去检查“中间行为是否合理”。这种问题,需要一个全新的测试维度。
1.2 需要盯住的核心评测维度
我给Agent测试定义了五个维度,也是每次CI必须采集的指标:
| 维度 | 关注点 | 典型度量方式 |
|---|---|---|
| 功能正确性 | 任务是否完成、完成质量 | 任务完成度评分、关键步骤是否达标 |
| 工具调用可靠性 | 工具选择是否正确、参数是否合法 | 工具选择准确率、参数Schema校验通过率 |
| 安全与合规 | 是否执行了危险操作、是否绕过权限 | 拦截器命中次数、越权行为检测 |
| 性能与延迟 | 端到端耗时、每轮交互响应速度 | 平均响应时长、P95时长、工具调用总耗时 |
| 成本消耗 | Token消耗是否在预算内 | 每次任务的Token数、折算金额、成本趋势 |
功能正确性是最复杂的维度,也是很多团队最容易做偏的地方。我建议不要用二分法(通过/不通过),而是用“完成度评分”。一个任务可以拆成多个子步骤,每个子步骤有独立的通过标准,最后汇总得到一个综合分数。比如“帮用户预定明天下午三点的会议室”这个任务,可以拆成:正确识别时间(15分)、正确选择会议室(25分)、调用预订接口(30分)、输出确认信息(15分)、未选择危险时段(15分)。总分100,超过90算通过。这样做的好处是,一个Agent如果只是输出格式差一点,可能还能得85分;但如果连会议室ID都搞错,基本上0分。分数维度能让我们清楚看到回归是“质量下降”还是“功能失效”。
1.3 测试金字塔的变体:菱形策略
传统测试金字塔是“底部单元测试最多、顶部端到端最少”。Agent测试我实践下来更像菱形——组件层最厚,集成层次之,端到端层最少但要精选。
原因很直接:Agent的单元测试(纯函数的、工具函数的、Prompt渲染的)运行极快且稳定,可以承担大量回归;端到端测试因为要调真实LLM,又贵又慢,只能跑精选场景。中间层的录制回放测试,既比单元测试覆盖更完整的链路,又比端到端测试稳定便宜,是两个极端之间的主力。
具体比例上,我当前项目里组件层约300个用例,集成层约150个用例,端到端约40个精选场景。跑一次完整CI,组件层大概3分钟,集成层10分钟,端到端分批跑约25分钟。如果全用例都要跑真实LLM,成本会翻十倍还不止。
2. 测试分层设计与核心实现
2.1 组件层:把Agent拆成可测的“硬逻辑”
很多团队一上来就测整个Agent链路,结果测试用例跑得又慢又不稳定,然后得出结论“Agent没法自动化测试”。但如果你把Agent拆开看,会发现里面混杂着两类东西:一类是硬逻辑(工具选择规则、参数解析、Prompt模板渲染、状态管理、结果格式化),一类是软逻辑(到底理解成什么意图、到底选择哪个工具)。软逻辑确实难以稳定断言,但硬逻辑完全可以。
我的建议是:在架构设计时就把硬逻辑抽成纯函数,不要藏在Agent的规划循环里。拿一个真实例子说,某个Agent在调用“发送邮件”工具前,需要一个函数validate_email_request(tool_input: dict) -> dict,负责检查收件人、主题、正文等参数是否齐全、格式是否正确。这个函数不依赖任何LLM,输入输出完全确定,就是完美的组件层测试对象。
def validate_email_request(tool_input: dict) -> dict: required = ["to", "subject", "body"] missing = [k for k in required if k not in tool_input or not tool_input[k]] if missing: raise ValueError(f"missing fields: {missing}") if not isinstance(tool_input["to"], str) or "@" not in tool_input["to"]: raise ValueError(f"invalid recipient: {tool_input['to']}") return tool_input对应的测试用例长这样:
import pytest def test_email_validation_missing_fields(): with pytest.raises(ValueError, match="subject"): validate_email_request({"to": "a@example.com", "body": "hello"}) def test_email_validation_invalid_recipient(): with pytest.raises(ValueError, match="invalid recipient"): validate_email_request({"to": "not-an-email", "subject": "hi", "body": "hello"})这类用例稳定、快、不费Token,是CI里最好的回归抓手。实测下来,一个中型Agent项目大概有40%的代码属于这类“可抽取的硬逻辑”,只要架构上做好职责拆分,测试难度瞬间降一个等级。组件层的核心思路就是:能写成纯函数的就不要放在Agent决策里,能用规则校验的就不依赖模型判断。
2.2 集成层:用录制回放隔离模型不确定性
必须承认,很多Agent逻辑没法抽成纯函数,它们天然依赖模型输出。比如用户说“帮我记一下明天上午十点开会”,Agent需要把这个输入解析成日历事件。这类测试不能每次调真实LLM,因为不稳定还费钱。我的实践方案是录制回放,也就是业界常说的HTTP VCR模式——第一次跑真实模型时把LLM请求和响应录下来,之后测试直接用录制的响应,不再真实调用。
# 伪代码:基于pytest的录制回放机制 import pytest from agent.core import agent_run @pytest.mark.vcr("responses/parse_meeting.yaml") def test_parse_meeting_intent(): result = agent_run("帮记一下明天上午十点开会") assert result.action == "create_calendar_event" assert result.params["time"] == "2025-12-10T10:00:00"录制回放有两个明显的坑。第一个坑是“刻舟求剑”——模型升级后,录制内容可能是旧模型的输出,测试过了但实际已经不同。我的解法是:录制文件标注模型版本和录制日期,每次升级模型后强制重录一次。第二个坑是“掩盖模型漂移”——如果模型行为已经变化,但测试还在用旧录制数据,等于没测。所以我给录制数据加了一个“保鲜期”,比如14天,超过保鲜期的录制文件在CI里标记为过期,触发重新录制。
集成层除了录制回放,还承担一项关键职责:工具调用参数的Schema强校验。Agent的规划再天马行空,最后落到工具调用的参数必须合法。我在项目里用JSON Schema对每个工具的输入参数做严格校验,校验失败直接抛异常并中止执行。这一点必须在集成测试里覆盖到,因为工具参数错误是生产事故的头号原因,而它又完全可以通过“硬校验”来拦截。
import jsonschema def validate_tool_call(tool_name: str, arguments: dict) -> None: schema = TOOL_SCHEMAS[tool_name] # 从工具注册表取Schema jsonschema.validate(instance=arguments, schema=schema)集成层是投入产出比最高的一层,既有足够的覆盖度,又不会因为模型随机性导致天天挂了。我建议把60%的测试预算投入到这一层,重点建设。
2.3 端到端层:任务完成度的综合评估
终于说到最头疼也最核心的部分:端到端测试。这一层不再去Mock或录制模型响应,而是在一个受控的沙箱环境里跑完整Agent链路,用真实模型去解决一个真实任务,然后对它的表现做综合评估。这里的核心矛盾是:任务完成质量无法用简单的断言判断。
我的方案是“Rubric评分法”。每个端到端用例都带一个评分卡,把任务拆成多个维度,每个维度有明确的得分标准和分值权重。以“查询库存并生成补货建议”为例,评分卡设计如下:
| 评分维度 | 满分 | 通过标准 |
|---|---|---|
| 意图识别正确 | 15 | 正确识别查询库存的意图,没有调用无关工具 |
| 工具选择正确 | 20 | 正确选择库存查询工具 |
| 参数完整合理 | 20 | 传入了商品ID和仓库代码且格式合法 |
| 结果解读正确 | 25 | 正确解读库存数据,给出合理结论 |
| 输出规范性 | 10 | 按要求的格式输出结果 |
| 安全合规 | 10 | 未调用任何危险操作 |
评分有两条路:规则自动评分和LLM-as-Judge。规则自动评分适合机器可验证的指标,比如工具选择是否正确、参数是否通过Schema、是否命中危险操作;LLM-as-Judge适合需要语义判断的指标,比如“结果解读是否正确”“总结是否清晰”。实际执行时,我会先让规则层跑一遍,规则层能判定的就直接打分,规则层不能判定的再交给Judge模型。
LLM-as-Judge本身就是一个不可靠的评测器,这里有几个经过验证的做法:第一,Judge模型与Agent模型不要用同一个,避免“自己给自己打分”的偏好;第二,每个需要Judge的维度至少用两个不同模型打分取平均;第三,Judge的Prompt里必须附上评分标准和正反示例,这样能显著提升打分的稳定性;第四,Judge生成后最好做一次盲评——把Agent输出打乱、不暴露Agent身份信息,减少刻板印象的干扰。
2.4 测试数据与Case的管理:测试集也是产品资产
测试用例数量和质量的维护,是Agent测试工程里最容易低估的工作。普通的接口测试用例写一次能跑两年,Agent测试用例生命周期非常短——产品功能变了要改,Prompt改了要改,模型升级了可能要更新评分卡,甚至用户一个反馈就能让你发现某个case设计得不合理。
我建议用Git管理测试集,不要只放在测试代码里。每个用例应该有独立的文件记录它的输入、期望行为、评测标准、适用的模型版本、创建人和最后修改时间。用例命名要规范到能直接看出来历,比如E2E_工具类_库存查询_成功路径_v2、INT_参数校验_邮箱格式_非法输入。
评测集的变更要有评审机制。任何新增、修改、删除都走一个轻量流程:提交变更说明,由测试负责人或技术组长审核,确认“这个case是在验证真实需求,而不是为了凑通过率”。这个流程执行不到位,会出现很恶心的现象——某个用例其实已经过时了,但为了CI绿着没人敢动它,整个测试集变成一个“政绩工程”。
3. 工业级CI/CD流水线集成
3.1 流水线阶段划分与任务编排
有了分层测试,下一步是把它们塞进CI/CD流水线,让每次提交和发布都能自动跑起来。我当前项目的Pipeline分六个阶段:
| 阶段 | 内容 | 耗时预算 | 失败直接阻断发布 |
|---|---|---|---|
| 1. 静态检查 | 代码规范、类型检查、安全扫描 | 3分钟 | 是 |
| 2. 组件层测试 | pytest跑硬逻辑单元用例 | 5分钟 | 是 |
| 3. 集成层测试 | 录制回放 + Schema校验 | 15分钟 | 是 |
| 4. 真模型E2E | 精选场景,分批执行 | 25分钟 | 是 |
| 5. 评测汇总 | LLM-as-Judge打分、生成测试报告 | 5分钟 | 是 |
| 6. 构建与发布 | 构建镜像、推送到制品库、更新环境 | 10分钟 | 是 |
阶段4(真模型E2E)是整个Pipeline里最容易成为瓶颈的环节,既要控制成本又要保障覆盖。我的做法是“风险分层分批”:核心业务场景每次提交都跑;重要场景每天定时跑一次;边缘场景每周跑一次。这样一个提交大概有12个E2E用例被即时执行,成本可控,又能保证最关键的路径不出问题。
流水线的编排有一个小建议:把阶段4拆成多个并行任务,按用例分组,每个任务独立分配资源。不要让40个E2E用例排队串行跑,那是拿时间换金钱,体验极差。
3.2 质量门禁(Quality Gate)怎么设
质量门禁是CI/CD集成的灵魂,没有门禁的流水线只是“自动化执行”,不是“质量保障”。门禁的指标必须明确给出数值阈值,并定义好触发条件是“阻塞发布”还是“仅告警不阻塞”。
我这里给出一组我实践后比较合理的默认值,你可根据实际情况调整:
| 指标 | 阈值 | 触发动作 |
|---|---|---|
| 功能完成度(E2E) | ≥90分 | <90 阻塞发布 |
| 关键Case通过率 | 100% | 任一失败阻塞发布 |
| 全部Case通过率 | ≥95% | <95% 阻塞发布 |
| 工具调用Schema校验失败率 | 0% | >0% 阻塞发布 |
| 单次E2E平均成本 | ≤预算的80% | 超阈值告警 |
| 端到端P95耗时 | ≤30秒 | 超阈值告警 |
这里有一个容易踩的坑:不要把所有指标都设成硬门禁。成本、耗时这类指标如果作为硬门禁,会因为模型API波动(比如上游服务偶尔变慢)导致发布频繁被打断,团队就会开始手动跳过门禁,门禁体系就形同虚设了。我的经验是,功能类指标一律硬门禁,性能和成本类指标先告警后人工判断,等数据稳定了再逐步收严。
门禁的另一个关键设计是“关键Case”和“全部Case”的区分。关键Case通常是用户高频核心路径,或者是发生过线上事故的回归用例。这些Case的通过率必须钉死在100%,不允许任何理由放过。技术上实现可以用pytest的mark机制给用例打标签,然后门禁脚本按标签分组统计。
# 统计关键用例通过率 pytest -m "critical" --junitxml=report-crit.xml # 统计全量用例通过率 pytest -m "e2e" --junitxml=report-all.xml3.3 环境管理与凭据安全
Agent测试环境的隔离要求比传统应用更严格,因为Agent会调用真实工具、访问真实数据。如果测试环境和生产环境没有做好隔离,Agent的一次错误操作就可能造成真实影响。
我的环境隔离方案分三层:第一层,网络隔离——测试环境部署在独立的网段,所有外部API调用统一走测试网关,禁止直连生产服务;第二层,工具Mock——对会修改数据的外部系统(比如真实订单系统、真实邮件服务)全部用Mock替代,只保留只读类服务(比如天气查询、公开知识库)的测试环境接入;第三层,危险操作拦截——在Agent的工具调用层加一个白名单拦截器,凡是“删除、清空、转账”等危险操作在生产环境一律拒绝执行,在测试环境需要显式开启才允许跑。
凭据管理上,我踩过一个挺坑的事:早期为了图省事,把测试用的API Key直接放到CI配置里,结果某次日志泄露导致Key被刷了几千块钱。现在项目的规范是:所有凭据必须放在CI系统的Secret管理里,流水线运行时通过环境变量注入;测试环境单独申请API Key,并设置每日调用上限和费用告警;每次测试报告里自动带上凭据的指纹(比如Key的后四位),方便追溯。这不是什么高深的技巧,但很多人真的没做。
模型版本管理也极其重要。Agent的行为和模型版本强相关,同一套代码配上不同版本的模型,测试结果可能天差地别。所以我在每个测试报告里强制记录模型名称和版本号(包括Prompt版本号),一旦线上模型升级,追踪回归来源时这个信息就是救命稻草。最尴尬的事情是线上出了故障,查来查去发现是两天前模型悄悄升级了,而测试报告里什么都没记。
3.4 灰度发布与持续回归策略
Agent应用上线不只是“发代码”,它涉及代码(Prompt逻辑、工具逻辑)、模型版本、甚至知识库数据三个变量的同时变更。这三个变量任何一边变化,都可能引起线上行为改变。所以我把Agent发布的流程设计成“三变量分别验证”:
- 代码变更:触发完整CI,通过后以小流量发布(比如5%流量),观察24小时再放量。
- 模型版本变更:不能直接改线上模型。先在镜像里固定新模型版本,配置一条“模型阵容对照”的定期回归任务,拿同一批E2E用例分别跑旧模型和新模型,对比完成度和成本。通过后才允许切流量。
- 知识库数据变更:影响最隐蔽。知识库的数据更新后,Agent的回答可能引用了新内容,但这个内容是否正确没人知道。这里我的建议是维护一个“知识库引用抽检用例集”,每次数据更新后,自动抽查20%的高频问题,确认Agent没有引用到错误或过期的条目。
回归策略上,完整测试跑的频率取决于团队迭代速度和业务风险。我的经验值供参考:每次提交(Merge Request)跑快速集(组件+集成+核心E2E),大概40分钟;每天凌晨跑全量集(所有E2E + 真模型对比测试);每周跑一次完整回归加模拟流量回放。这样既不会在合并时等待太久,又能保证全量覆盖不遗漏。
4. 工具链选型解析
4.1 测试框架:pytest还是自研Runner
Agent测试的不同层需要不同工具,我的选型经验是:组件层和集成层用pytest足够,端到端层和评测汇总阶段通常需要少量自研脚本。
选型pytest的原因很实在:参数化插件可以方便地把多组输入喂给同一个测试用例;Mark机制能按“组件/集成/端到端/关键/重大”打标签,配合命令行做灵活筛选;重试插件能处理偶发性网络抖动(但要克制使用,重试只能掩盖问题不能解决问题);最后JUnit报告可以被大部分CI平台原生识别,省去对接成本。
端到端层我建议自研一个轻量Runner,核心需求是并发控制、成本统计、进度感知。E2E任务通常要并行跑多个会话,每个会话调LLM都有成本,Runner需要实时统计Token消耗、记录每个用例的开始结束时间、失败后自动收集日志和Trace。pytest当然也能做,但当你需要跑在Kubernetes或者按风险动态分配用例时,自研Runner往往更顺手。我的建议是:先用pytest把完整闭环跑通,当你遇到“跑不动”“统计不了”“并发控不住”三个瓶颈中的任意两个时,再动手自研。不要一上来就自研,容易过度设计。
4.2 评估器与Judge模型选型
LLM-as-Judge已经是Agent测试绕不开的环节,但它的可靠性问题也必须正视。我实测下来的经验是:Judge模型对同一份Agent输出打分,不同模型可能差20分以上;同一模型不同运行批次,方差也有5分左右。这种波动对质量门禁是致命的——一个Case昨天96分通过,今天93分不通过,Agent根本没变,纯纯是打分波动。
为此我有几个缓解措施。第一,Judge温度必须设0,尽可能降低抽样随机性。第二,每个需要语义判断的维度至少用两个不同品牌的模型打分取平均,模型之间的偏差能相互抵消一部分。第三,Judge Prompt必须携带评分标准和参考示例,实测下来,有示例和无示例的打分方差差距很明显。第四,评分结果落库后要做历史趋势分析,某一维度若持续下降但Agent没改,基本可以断定是Judge漂移了,需要重新校准。
检索式评估是与Judge互补的另一类方法,特别适合有“标准答案”的任务。比如“上海今天天气怎么样”,不管Agent怎么组织语言,最后输出里应该包含“晴/雨/温度”这类关键信息。我可以写一组关键词或正则,从Agent输出中检索并打分。这个方法的优点是稳定、成本为零,缺点是只能覆盖有明确事实结论的场景。实际评估时我是两类混着用:能检索的优先检索,检索不了的才上Judge。
4.3 模拟器与沙箱搭建
Agent测试环境的沙箱搭建,是很多从传统测试转过来的同事容易轻视的点。Agent会调用一堆外部工具,你要给每个工具准备一个测试替身。半个实战经验是:优先用“录制回放式Mock”,而不是硬编码写死返回值。因为硬编码Mock会丢掉真实服务的响应结构和异常模式,录制回放至少在真实语义上更接近线上。
# 伪代码:把外部工具封装为可切换模式 class InventoryClient: def __init__(self, mode="record", cassette_path=None): if mode == "record": self.handler = RecordingHandler(cassette_path) elif mode == "replay": self.handler = ReplayingHandler(cassette_path) elif mode == "live": self.handler = LiveHandler()危险操作拦截一定要在工具层做,而不是在Agent层做。我的拦截器实现思路是:每个工具注册时声明“危险级别”,调用链路上有一个统一的Interceptor,当检测到危险级别高的工具被调用时自动记录日志、发送告警,并按策略决定是放行还是阻断。在CI环境里,统一设成阻断模式;在预发布环境设成放行但告警模式。这套逻辑同时还能用于安全门禁,CI里只要检测到Agent在测试过程中触发了危险工具,该用例直接判失败。
4.4 CI/CD平台与流水线配置要点
最后聊一下CI平台上的具体配置。Agent测试和传统构建任务差异很大,有几个配置必须专门处理。
第一个是超时控制。Agent每个E2E用例的正常耗时可能在20到40秒,但遇到模型API卡顿或者Agent进入死循环,可能跑5分钟都不结束。所以超时时间必须显式配置,还要针对“单用例超时”和“整批次超时”分别设置。我项目中单用例超时是120秒,整批次超时是30分钟,超时即失败并自动抓取堆栈。如果没有这个兜底,Pipeline会卡在某个Agent的死循环里,白白烧钱。
第二个是并发和资源隔离。E2E任务不能和普通构建任务共享同一批Runner,因为Agent测试会调用外部API、占用CPU和内存,可能影响其他任务的稳定性。我在CI系统里给E2E任务单独配置一组合Agent执行器,按需自动扩容,并设置最大并发数防止API调用量激增。
第三个是报告输出。测试结果不能只停在控制台上,需要结构化输出供门禁脚本、监控大盘、告警系统消费。我每次流水线结束后会输出三个文件:JUnit XML给CI平台展示用例级结果,自定义JSON包含成本、耗时、完成度等指标,以及一个带Trace链接的HTML报告方便人工排查。成本指标和耗时指标需要同步到大盘,方便观察趋势。
5. 常见问题排查与经验实录
5.1 flaky测试:判定、根治与妥协
只要是Agent测试团队,flaky(不稳定)测试一定是最大的心头痛。我见过太多团队因为flaky测试放弃自动化,太可惜了。实际上,flaky测试不能靠“加个重试”糊弄过去,要做根因分类再对症下药。
我遇到的flaky原因大概有四类,每类对策不一样:
| 症状 | 可能原因 | 排查方法 | 解决方向 |
|---|---|---|---|
| 同样的输入,结果时好时坏 | 模型随机性(温度>0) | 检查测试配置 | 温度归零;关键断言改为“语义等价匹配” |
| 一批用例里就个别的偶尔挂 | 外部工具服务不稳定 | 看工具响应耗时和错误码 | 加超时和重试;区分“Agent错误”和“环境错误” |
| 用例依赖执行顺序 | 环境残留或状态污染 | 单独跑这个用例看是否还挂 | 测试前置清理;装饰器强制指定依赖项 |
| 模型升级后突然大面积失败 | Prompt或用例未同步更新 | 查看最近模型版本变更 | 模型版本锁定;升级时强制重录测试数据 |
一个能根治很多flaky问题的通用技巧是,把测试断言从“精确匹配”改为“语义匹配”。比如断言Agent输出包含“会议时间下午三点”,不要去匹配"15:00"或者"下午3点"这种具体格式,而是用检索式评估确认它的含义。我项目中写了一个assert_contains_semantic(full_text, keywords)的帮助函数,把“必须包含核心语义”的判断从字符串匹配里解放出来。
5.2 评估结果不稳定:Judge漂移和评分离散
前面说过Judge模型本身的波动问题,这里展开讲一下排查路径。当某个用例的评分出现明显跳动时,我的排查顺序是:先看评分原始分数,拖出Judge打分记录的JSON,确认几个Judge模型是否一致地给了高分或低分;如果Judge之间严重分歧,说明这个用例的评分标准不够清晰,需要补充示例或拆分成更细的维度;如果Judge稳定但评分和上次差异很大,那大概率Agent输出真变了,需要倒查是代码改动、Prompt改动还是模型版本变更导致的。
还有一种隐蔽的漂移:Judge模型供应商静默升级了他们的模型,导致整体打分尺度变化,但所有用例通过率出现“系统性上升或下降”。这种情况最坑,因为它看起来像Agent变好或变坏了,其实是打分基准变了。应对办法是维护一份“基准评分集”:20个历史用例,每周保持不变,每次用当前Judge模型给它们打分,如果基准集分数波动超过8%,就要警惕Judge漂移并考虑升级测评体系或者重新录制基线数据。
5.3 成本失控:Token预算保卫战
Agent测试成本比传统测试高一两个量级。我见过一个团队,全量E2E跑一次要烧掉数千元的模型费用,还没等测试体系建完,预算先爆了。控制成本的核心不是“少跑”,而是“聪明地跑”。
第一层手段是分层筛选:静态检查和组件测试几乎没有成本,所以能下沉的测试尽量下沉;录制回放已经能够覆盖的集成场景,不要随便升级到真实模型。第二层是缓存复用:同一个用例短期内多次执行时,命中响应缓存就不重复调用;Prompt完全一样的请求系统直接返回缓存结果。第三层是小模型预筛:一些简单的任务(比如意图识别是否准确)可以先用小模型跑一遍,通过率明显不行的话直接判失败,不用再花大模型的钱。第四层是按批次预算控制:给每个测试批次设置Token上限,超过后自动停止后续用例并标记为“未执行”,这个机制能防止Pipeline卡死导致无上限烧钱。
我在项目里给每个用例都标注了预估Token成本,E2E用例的成本上限是单次0.5元,超过的直接在代码审查阶段被拦下。这样从源头控制,后面才不会失控。
5.4 测试Case频繁失效:维护闭环怎么做
很多团队在Agent测试上栽在“用例维护跟不上业务迭代”。周一改了个Prompt,周二更新了工具注册信息,周三模型升级,周四就会发现测试集红了一片。这时候很多人自暴自弃:“Agent测试根本没法做。”
我的实践是两个机制。第一个是每周一次的失败用例Review例会(线上异步即可)。把所有失败用例按原因分类:产品变更、模型漂移、用例过时、测试环境问题、真实Bug。产品变更的更新用例输入和预期行为;模型漂移的评估是Agent退化了还是用例标准设计不现实;用例过时的直接修改或删除;真实Bug的提给研发修复。这个流程坚持三个月,你的测试集就会和企业业务保持同步。
第二个是“用例所有权”制度。每个用例必须指定一个Owner,谁更新、谁删除、谁对它的稳定性负责。没人负责的用例,本质上就是没人关心的僵尸用例,一旦出了问题也没人愿意修,最后变成一堆红色状态挂在CI上。
5.5 排查流程速查
| 现象 | 第一步动作 | 快捷定位方向 |
|---|---|---|
| CI红但在本地跑是绿的 | 对比CI与本地环境变量、模型版本、依赖版本 | 用固定seed重跑并记录完整流转日志 |
| 单个用例重试后就通过 | 先不要跑下一次,保存当前失败Trace | 判断是偶发还是时序问题 |
| 多个E2E集体变差 | 拉最近一次模型版本和Prompt版本变更记录 | 倒回旧版本做AB对比 |
| 评分异常高但实际行为不对 | 检查Judge模型及评分Prompt版本 | 抽查几份Judge打分原始记录 |
| 成本突然飙升 | 查看新增用例、重试次数、响应缓存命中率 | 找出消耗最多的Top10用例 |
写在最后
我做这件事最大的体会是:Agent的自动化测试,难的不是技术,而是你愿不愿意接受“测试结论是概率性的”这一现实。接口测试告诉你“对不对”,Agent测试告诉你“有多对、平均对不对、是不是比上次差”。学会和这种不确定性共处,然后通过多维度采集、多模型评估、历史趋势对比把不确定性压缩到一个可控区间,这条路是走得通的。
最后分享一个我踩了无数次坑换来的建议。不要一开始就追求测试覆盖率的数字,也不要一上来就铺全量的E2E。先挑出十个最高频的核心业务场景,把它们变成高质量的E2E用例,配合组件层和集成层的快速回归,把发布流程跑顺。等这套机制稳定了,再逐步增加用例量。测试体系是一步步长出来的,不是一次性建成的。记住一个原则:测试集也是要长期维护的产品,用例的维护投入和用例编写投入至少要五五开,否则你只是在给自己制造一堆定时炸弹。