首页 / 资讯中心 / 文章详情

Agent 的单测到底怎么写?我把 Java 测试金字塔搬过来,撞了三堵墙

Agent 的单测到底怎么写?我把 Java 测试金字塔搬过来,撞了三堵墙 ★ FEATURED ARTICLE
刚接手 Agent 项目那阵领导问这块你们怎么保证质量我张口就是照测试金字塔来单元测试打底。话说完就被现实打脸——我连一个稳定的单测都写不出来。为什么因为我撞上了三堵墙。第一堵墙没有正确答案Java 里写单测assertEquals(2, add(1, 1))答案唯一。但 Agent 的输出呢你让它总结这篇文档正确的总结有无数种写法。你写assertEquals(预期总结, actual)模型换个措辞测试就红了而且红得莫名其妙——它没错只是和你想的不一样。这就是第一堵墙Agent 的输出没有唯一真值断言相等这条路走不通。第二堵墙非确定性同一个输入、同一个模型跑两遍结果可能不一样。今天测试全绿明天同一个测试全红你盯着 diff 看半天最后发现是模型的采样随机性。这种 flaky 在 Agent 测试里是常态不是偶发。第三堵墙慢和贵单测要快最好毫秒级。但 Agent 的每一次调用都要真打模型一次几秒到几十秒还要花钱。几百个单测跑一遍光 token 费就够喝一壶。解法把 Agent 拆成两层来测想通之后就是一个思路别把 Agent 当黑盒整体去测把它拆开。拆成确定性部分和智能部分。确定性部分——prompt 模板、工具选择、参数装配、流程编排、后处理——这些不依赖模型可以像普通 Java 代码一样单测。智能部分——模型到底理解得对不对、总结得好不好——这一层没法单测只能用评测。确定性层mock 掉模型关键技巧就一条把模型的输出 mock 掉专测你的逻辑。比如你的 Agent 接到查订单 O2026应该调queryOrder工具并传对参数。这个流程可以这样测// 让模型返回一个工具调用而不是真的调用模型when(chatModel.call(any(ChatRequest.class))).thenReturn(finishWithToolCall(queryOrder,Map.of(orderId,O2026)));// 跑一遍 Agentagent.run(帮我查一下订单 O2026);// 断言工具真被调了且参数正确verify(orderService,times(1)).queryByIdAndUser(O2026,currentUser);这里 mock 掉的是模型验证的是你写的 glue code——工具路由、参数装配、身份注入这些你真正负责的代码。这才是你该单测的地方。再比如测工具调用失败会不会走 fallbackwhen(chatModel.call(any())).thenReturn(finishWithToolCall(queryOrder,Map.of(orderId,O2026)));doThrow(newBizException(订单不存在)).when(orderService).queryByIdAndUser(any(),any());agent.run(查订单 O2026);// 断言失败后触发兜底逻辑而不是直接抛出去verify(fallbackHandler,times(1)).handle(any(),any());智能层用评测代替断言对于模型答得好不好别写单测写评测属性断言不强求等于而是断言输出满足某些属性——“包含订单号”“提到了退款金额”“没泄露手机号”。给模型留表达空间只卡关键事实。LLM-as-judge用另一个模型当裁判打分配上评测集Ragas、DeepEval 那套思路跑回归。金标准集攒一批输入 → 期望要点的语料每次改动跑一遍看达标率跌没跌。写在最后纠结半天结论就一句话Agent 里你能单测的只有不依赖模型的部分依赖模型的只能评测测不了对错。所以别再拿测试金字塔直接往 Agent 上套。真正值钱的是那条分界线——把你的逻辑从模型的智能里切出来。切得越干净能单测的越多质量越有底。这也是我在设计 Agent 时特别强调把 glue code 写薄、把工具写成可 mock 接口的原因——不是洁癖是想测它。
阅读完成 · 觉得有帮助?
咨询建站