1. 核心思路:为什么AI开发必须测试驱动
先说一个挺现实的现状。这两年大模型应用开发、AI Agent开发的项目越来越多,但我见过的团队里,真正能把交付节奏稳定住的,往往不是那些“提示词写得特别花”的,而是那些愿意把测试前置、把任务拆清楚的。这个现象一开始我也没太在意,直到自己在一个RAG项目上连续踩了三次“修完这个问题,冒出那两个新问题”的循环之后,才算彻底想明白一件事:AI开发的不确定性,天然需要外部约束,而这个约束最好的来源,就是测试。
1.1 传统开发的TDD经验不能直接搬
传统的测试驱动开发(TDD),核心循环是红-绿-重构:先写一个失败的单测,再写让测试通过的最小实现,然后重构。这套方法论在确定性系统里非常好用,因为函数输入输出基本可预期,行为边界是清晰的。但一旦进入AI应用开发,事情就变了:
- 模型的输出有随机性,同样的输入,温度和top_p不同,结果可能差一大截。
- 提示词工程的改动往往是“牵一发动全身”,这边把格式修好了,那边逻辑又开始漏。
- Agent开发里,工具调用的顺序、分支选择、兜底策略,叠加起来是一个巨大的状态空间,根本没法用传统的穷举式单测覆盖。
我见过不少团队拿着传统TDD的套路硬套AI项目,最后结果都是测试写了一堆,但没有一个能稳定跑过三天,维护成本高到让人怀疑人生。所以在AI开发里,测试驱动不是说“先写单元测试再写代码”,而是说:先用架构和测试策略把任务的边界锁死,再动手开发。
1.2 测试驱动AI开发的本质:用测试锁定任务边界
后来我自己做AI Agent开发的项目多了,慢慢总结出一个相对能用的方法,也是这篇文章标题里想讲的:把架构和测试策略当作任务分解的输入,先想清楚“怎么测”,再确定“做什么”。这句话听起来有点绕,我换个方式解释。
你在拆分一个AI开发项目的时候,不是按“功能模块”一刀切,比如先做文档解析、再做向量检索、最后做生成回复。而是先想清楚:这个项目里,哪些部分是确定性逻辑,哪些部分是模型行为,各自用什么方式验证。这个“验证方式”就是任务分解的核心依据。
说得直白一点:任务不是按模块拆的,是按测试策略拆的。 确定性逻辑用单测锁行为,模型输出用黄金测试集锁质量,集成链路用契约测试锁交互。不同的测试策略对应不同的任务边界和责任范围,每一步做完了,都有明确的验收信号。
这个思路在和架构结合之后更落地。比如你做一个分布式的AI Agent系统,上游是意图识别服务,中间是任务规划引擎,下游接了一堆工具调用。每个服务之间的接口定义,本身就是契约测试的天然载体。接口定清楚了,测试写出来了,任务自然就被拆成了几个可以并行推进、独立交付的单元。
所以整个方法论的核心逻辑是:架构先行(确定边界和交互方式),测试策略跟上(确定每个边界如何验证),任务分解随后(依据验证方式切分交付单元)。这套逻辑下来,AI开发不再是“边写提示词边看效果”的玄学,而是一条看得见、测得到、能卡进度的流水线。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 架构先行与测试策略设计:先将边界定型再动手
2.1 架构先行的核心:明确边界才能谈测试
做AI应用开发,很多人习惯拿到需求就开写,先调通一个能跑的Demo再说。这个思路在原型验证阶段没有错,但一旦项目进入正经交付阶段,问题马上暴露:提示词散落在代码里,数据处理逻辑和模型调用糊在一起,不同的模块之间通过隐式的文本格式约定交互,根本没法做针对性测试。
我自己的习惯是,在任务分解之前,先花半天到一天把架构图画出来。不是那种特别复杂的UML图,而是一个清晰的模块划分和数据流向图。重点回答几个问题:
- 输入数据从哪来,格式是什么,进入系统后第一步做什么归一化处理。
- 哪些模块调用大模型,哪些模块是纯逻辑处理,哪些模块是外部工具/服务交互。
- 模块之间传递的数据结构是什么样的,用Pydantic模型还是TypedDict,字段约束有哪些。
- 错误处理和兜底策略在哪一层做,每层的失败标准是什么。
有了这个架构图之后,测试策略就有地方落了。每一个模块边界,就是一条测试线。纯逻辑模块用单元测试覆盖边界条件,模型调用模块用黄金测试集锁输出质量,跨模块交互用契约测试锁接口兼容性。这里有个特别需要注意的地方:AI应用里最容易出bug的不是模型本身,而是模型输出和下游代码之间那个隐含的约定。模型漏了一个字段、多了一段解释性文字、格式稍微偏了一点,下游解析代码就炸了。
分布式架构的场景下,这个问题更明显。我做过一个多Agent协作系统,每个Agent以一个独立服务的方式运行,Agent之间通过消息传递协作。刚开始最头疼的问题就是Agent A返回的JSON结构到了Agent B那里解析失败,因为两边对字段的定义没有对齐。后来专门给每个Agent的输入输出定义了严格的JSON Schema,然后用契约测试锁住了这些接口,这个问题才算彻底解决。
2.2 测试策略设计:AI项目的测试金字塔要重构
传统软件的测试金字塔是:底层大量单元测试,中间少量集成测试,顶层极少数端到端测试。AI项目里这个金字塔一定要重构,因为模型行为这一层没法用传统的单元测试覆盖,你没法断言“模型应该返回某个具体值”,只能断言“模型返回的结果应该满足某些约束”。
我自己实践下来,AI项目实际有效的测试策略大概是这个结构:
code复制顶层:少量端到端演练测试(验证核心用户路径)
中间:集成链路测试(串联真实组件,重点验证接口契约和数据流转)
底层:大量确定性子测试(规则引擎、解析器、提示词模板渲染、数据清洗、工具参数校验)
黄金测试集:独立于金字塔之外,覆盖关键场景,跟踪质量回归
这个结构的核心逻辑是:把所有确定性的逻辑尽可能拆出来,用底层测试锁死;模型相关的部分,用结果约束和样本集来测;真正的链路问题,靠集成测试兜底。
举几个具体例子:
- 提示词模板渲染是一个纯函数,输入参数,输出字符串,这个必须单测。
- 工具调用的参数校验是确定性的,模型说要调用某个工具,参数缺了必填字段就得报错,这个也可以单测。
- 模型返回的结果解析器,能处理正常格式、缺字段、多字段、类型错误等情况,这个是单测的重头。
- 真正需要模型输出的部分,不做断言具体的文本值,而是断言结构正确、关键信息是否存在、是否符合约束规则。
这里有个核心心法:不要和模型输出的具体文本较劲,而是和模型输出的结构约束较劲。 你测的是“能不能正确提取出JSON里的name字段”,而不是“name字段的值是不是叫张三”。前者是确定性的,后者是碰运气。
2.3 测试策略反过来修正架构
架构先行的好处是,它会反向暴露很多你之前没考虑到的模块。尤其是当你认真设计测试策略的时候,你会发现自己需要一些“额外的”模块,这些模块在正常功能开发时经常被忽略:
- Schema校验层:模型输出进系统之前,先过一层结构校验,不满足就触发重试或修复逻辑。
- 可观测性模块:记录每次模型调用的输入输出、耗时、token消耗,方便问题回溯。
- 测试夹具与Mock策略:模型调用的Mock怎么设计,延迟怎么模拟,失败怎么注入。
这些模块都不是核心业务功能,但没有它们,整个系统的可测性和可维护性都无从谈起。这也是为什么我强调架构先行:如果你先定好了测试策略,架构图里自然会留出这些模块的位置。如果你只按功能需求画架构,这些模块大概率会被漏掉,然后后面找补的时候痛苦万分。
我在做智能体工具调用功能的架构设计时,专门加了一个工具调用参数校验的模块,所有工具在真正执行之前,参数必须过一遍JSON Schema校验。当时团队里有人觉得这层有点多余,明明模型已经生成了参数JSON,直接执行不就行了。结果后来模型连续几次生成了缺少必填字段的参数,执行端报错报得莫名其妙。加入校验层之后,错误能提前拦截,还能触发自动修复机制让模型重新生成,问题就清晰多了。
3. 实操过程:任务分解模板与测试先行落地
3.1 从架构和测试策略到任务清单:一个减法过程
架构和测试策略定了之后,任务分解就变成了一道减法题。我用的是这套流程:
- 把架构图里的每个模块列出来。
- 为每个模块标注它的测试策略(单测为主、契约测试为主、黄金样本集为主、或者纯人工验证)。
- 把测试策略需要的支持组件也列出来,比如测试数据准备、Mock服务、Schema定义。
- 合并同类项,比如多个模块需要共享的测试夹具,就单独抽成一个前置任务。
- 按依赖关系排序,让任务形成一条可执行的流水线。
- 最后给每个任务明确“完成的定义”,也就是它的测试验收标准。
举个例子,我最近在做的一个智能客服助手,架构上分了三层:输入理解层(意图识别、实体抽取)、知识检索层(向量检索、重排序)、答案生成层(上下文组装、大模型生成、格式修正)。测试策略对应是:
- 输入理解层的意图分类,用Golden Set测分类准确率,准确率低于阈值就打回。
- 知识检索层的召回率,用标注好的查询-文档对来测。
- 答案生成层的输出,测结构约束和不含有害内容,不测具体措辞。
- 整个链路的串联,用几个端到端的场景用例。
基于这个拆法,任务被分成了大概15个,每个任务都有明确的验收测试。开发的时候照着任务清单逐项推进,每完成一个就红一遍测试,全绿了再进下一个。整个项目进度变得非常透明,哪块有风险一目了然。
3.2 一个可复用的任务模板
具体到每个任务的拆分,我习惯用这个模板来定义,这个模板在AI Agent开发里特别实用:
json复制{
"task_id": "TASK-001",
"task_name": "模型输出Schema校验模块",
"objective": "为所有模型输出提供统一的格式校验能力,确保下游解析不因格式问题崩溃",
"ac_test": {
"input_format": "模型原始输出文本",
"expected_output": "校验通过或失败的原因列表",
"edge_cases": [
"合法JSON",
"非法JSON",
"缺少必填字段",
"字段类型错误",
"多余字段",
"空字符串"
]
},
"dependencies": ["TASK-000: JSON-Schema定义"],
"definition_of_done": "所有edge case用例通过单元测试;集成测试中模型输出经过校验层的失败率低于1%"
}
每个任务都绑定一个验收测试(AC Test)。这个AC Test不是摆设,它是任务是否完成的标准答案。写代码之前先把AC Test的红色状态跑出来,代码写完之后再跑到绿色。这听起来有点像传统TDD,但这里的“测试”粒度更大,更偏行为级而不是函数级。
3.3 写测试的几个实战细节
光有模板不够,AI项目的测试执行细节也相当考验功力。我在实际项目中总结了一些比较关键的操作要点,分享几个印象最深的:
第一,Mock模型调用要分“灰盒”和“黑盒”两种。 单元测试里,模型调用应该被Mock掉,这是黑盒,只测业务逻辑。但集成测试必须走真实模型调用,哪怕慢一点、贵一点,也要跑,这就是灰盒。只有灰盒测试才能真正暴露出模型行为变化带来的影响。我在一个项目里见过只做黑盒Mock测试的情况,看起来全绿,部署上线后真实模型一跑就崩,就是因为Mock太完美,把模型偶尔的噪声行为过滤掉了。
第二,黄金测试集要持续维护和扩展。 每发现一个线上问题,修复之后,要把这个问题的输入补进黄金测试集。这样黄金测试集就是整个项目质量的“记忆库”,每天都在变厚。而不是说项目上线了,测试集就固定不变了。我做Agent开发时还额外给每个Agent建立了一个“行为档案”,记录它在不同输入下的输出模式变化,方便追踪模型升级带来的行为漂移。
第三,确定性逻辑和模型逻辑要物理隔离。 写测试的时候你一定会发现,有些代码测起来很痛苦,改起来很顺畅。痛苦的原因通常都是因为确定性逻辑和模型逻辑耦合在一起。比如直接在回调函数里写提示词模板渲染,或者直接把模型输出扔给下游函数。物理隔离的做法是:所有进入模型前的文本,必须先经过独立的构造器;所有从模型返回的文本,必须先经过独立的解析器。这样两边的测试就可以完全互不干扰。
我记得有一次,一个函数里既有模板渲染又有模型调用又有结果解析。测试时,为了测模板渲染,得先Mock掉模型调用;为了测结果解析,又得先模拟模型输出。搞得每次改逻辑,测试代码也跟着大改。后来把这个函数拆成三个独立模块,每个模块的测试复杂度瞬间降了一个量级。那之后我就把“物理隔离”当成一条硬性规则了。
3.4 用测试驱动的方式推进一个AI Agent功能开发
下面用一个具体的场景,演示从任务分解到测试先行的完整流程。
场景:开发一个能自动理解用户问题并调用工具完成日程安排的AI Agent。功能涉及意图识别、槽位提取、工具调用、结果反馈四个环节。
第一步是架构设计,我大概画了这么几条边界:
- 用户输入 -> 预处理模块(清洗、格式化)
- 预处理结果 -> 意图识别模块(模型调用)
- 意图和槽位 -> 任务规划模块(规则引擎,根据意图路由到具体工具)
- 规划结果 -> 工具调用模块(参数组装、执行)
- 工具返回 -> 回复生成模块(组织自然语言反馈)
第二步是设计测试策略:
| 模块 | 测试类型 | 黄金测试集/用例 |
|---|---|---|
| 预处理 | 单元测试 | 各种输入格式、特殊字符、空输入 |
| 意图识别 | 黄金测试集 | 20个核心意图,每个3-5个变体表达 |
| 槽位提取 | 黄金测试集 | 日期、时间、地点、人员等实体 |
| 任务规划 | 单元测试 | 规则表全覆盖,每个意图到工具映射一条用例 |
| 工具调用 | 单元测试+契约测试 | 参数校验、超时、异常返回 |
| 回复生成 | 约束测试 | 必须包含工具执行结果的关键信息 |
第三步是任务分解,按照模块和测试策略拆:
- 任务1:预处理模块 + 单测。验收:所有输入格式用例通过。
- 任务2:意图识别模型接口 + 黄金测试集。验收:准确率不低于90%。
- 任务3:槽位提取 + 黄金测试集。验收:关键实体提取率不低于85%。
- 任务4:任务规划规则引擎 + 单测。验收:所有意图-工具映射正确。
- 任务5:工具调用模块 + 参数校验单测 + 契约测试。验收:非法参数100%被拦截。
- 任务6:回复生成模块 + 约束测试。验收:所有回复包含工具执行结果关键信息。
- 任务7:集成链路测试。验收:全流程端到端场景全通过。
最后下场开发时,每完成一个任务,先跑对应的测试,红色就是没写完,绿色才算完成。整个过程下来,团队对项目状态的感知是实时的,谁在哪个环节出了问题,哪块的质量不达标,一目了然。
这套打法最大的价值在于:你不必在“不知道哪天能做完”的项目里焦虑,因为每一步都有硬性的验收标准,心里有底。
4. 常见问题与排查技巧实录
4.1 AI测试驱动开发翻车现场
方法归方法,实际执行的时候坑是真不少。下面列几个我踩过或者看别人踩过的典型问题,每个都是真金白银换来的经验。
测试与开发顺序本末倒置。 团队里有人把TDD理解成了“先写测试再写实现”,结果为了赶进度先把实现写完了,测试只是补了个形式。最后的效果是测试全绿,但到底在测什么没人说得清,换个人接手代码直接懵。我的应对经验是:任务级AC Test必须提前写出来,并且在任务启动前就和团队成员对齐。AC Test是不是有效,最好的检验方式是让它先红一次。如果它在开发前跑是红的,就说明它能测出“没实现”的状态,这是合格的测试。
黄金测试集过拟合。 某个项目的意图识别测试集准确率达到了97%,看着很漂亮,上新场景后准确率直接掉到70%。原因就是测试集和训练样本高度重合,基本等于考试漏题。解决方法是:黄金测试集至少有一部分是“从未见过的新样本”,最好从真实用户反馈里持续补充。准确率指标不是越高越好,能在新样本上稳定才是真本事。
LLM输出不稳定的测试误判。 集成测试里断言了模型输出的具体措辞,比如“回复中必须包含‘已为您安排’这几个字”。模型换了个版本或者温度稍作调整,措辞变成了“您的日程已安排”,测试直接红了,但其实功能完全没坏。这就是和模型输出较劲的典型翻车案例。正确做法是断言语义约束,比如“回复必须包含会议时间、地点、参会人这三个关键信息”,而不是锁死具体文案。如果你的测试出现了“因为模型换了个说法就挂掉”,大概率是断言方式有问题。
过度Mock导致测试失真。 前面提过,单元测试Mock模型调用没问题,但集成测试也大张旗鼓地Mock,就会掩盖真实链路的问题。我有一个比较实用的判断标准:如果Mock层数超过两层,测试的参考意义就大打折扣。该花的token钱要花,该等的延迟要等,不然测试环境一片绿,生产环境一团黑。
任务写得太粗没法卡进度。 “实现知识库问答功能”这种任务,验收标准是什么完全说不清,开发者和验收者对“做完”的理解往往不一样。我这边的经验是,任务粒度以“一两天内能完成,并且有明确验收信号”为准。如果某个任务预计超过两天,说明它还需要继续拆。拆分的依据不是开发工时,而是测试策略有没有变化:如果一部分用单测、一部分用黄金测试集,那这两部分就必须拆开。
4.2 问题排查速查表
顺手整理一个排查表格,大家遇到问题可以先对照自查:
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 测试全绿但线上出问题 | 测试环境Mock过度 | 检查集成测试是否使用真实模型 |
| 单测经常性红 | 测试依赖模型输入输出 | 将模型调用隔离出单元测试范围 |
| 黄金测试集指标虚高 | 测试集与训练集重复 | 补充真实用户新样本,独立评估 |
| 模型升级后测试大量失败 | 结果断言太死板 | 将文本断言改为结构化断言 |
| 任务推进卡壳 | 任务拆分过粗 | 按测试策略重新拆分,确保粒度可验收 |
| 契约测试通过但联调失败 | 契约定义了但没对齐实现 | 检查契约是否是双方共同维护而非单方定义 |
| 工具调用偶发失败 | 模型生成的参数不规范 | 加入参数校验层并设计自动修复机制 |
| 回复内容不满足要求 | 提示词约束不足 | 用约束测试定位具体缺失字段,回写提示词 |
这些问题的通用归因其实都指向同一个根源:没有把确定性和不确定性分开管理。 模型行为是不确定的,交给黄金测试集和约束测试去管理;业务逻辑是确定的,交给单元测试和契约测试去锁死。两者混在一起,项目就成了一团乱麻。
4.3 工具选型与落地参考
这套方法论落实下来,工具选型不用太复杂,够用就行。我目前主力的技术栈是Python + Pytest,配合Pydantic做Schema校验,再用Github Actions做CI,每次提交自动跑测试。涉及Agent开发时,会额外用一些Mock框架模拟工具调用的各类场景。这里特别注意,别为了测试去搭一套特别重的平台,AI开发初期的核心矛盾是方法论能不能落地,而不是工具是不是炫酷。
如果你看不明白某些测试理论说的“属性测试”“模糊测试”该怎么用,建议先跳过,从最简单的单元测试和黄金测试集开始。这套打法跑顺了,再看更高级的技巧,自然就能理解它们解决的是什么问题。
5. 个人实操经验与建议
文章写到这,方法论基本已经完整了。最后聊一点我个人的体会,算是给想上手的朋友的一点叮嘱。
这套“架构和测试策略驱动的任务分解方法”,最难的部分不是写测试,也不是画架构,而是改变开发习惯。大部分AI开发者习惯了快速试错、看效果说话,要让他们先想清楚“怎么验证”,再动手写代码,需要一段时间的刻意练习。我自己的建议是,不要在一个大项目上直接全面上这套流程,而是先从一个小模块开始跑通。比如只针对“模型输出解析”这一块做测试先行,跑通之后再逐步扩展到其他模块。
跑完几个迭代之后你会发现,测试驱动真正带来的不是“测试覆盖率”这个数字,而是开发节奏的确定性。在AI应用开发这个变量超多的领域,有一群稳定的绿灯测试在那里兜底,你做功能迭代和重构的时候心里会踏实很多。
还有一点比较重要的是,测试驱动AI开发不是让你不写提示词了,提示词工程依然是核心工作。只不过提示词的每个版本迭代,都应该有对应的“评测依据”——要么是黄金测试集的准确率变化,要么是约束测试的通过情况。只有这样,提示词的改进才是可持续的,而不是每次调试都像在赌运气。
我在结尾处就不是总结了,分享一个我自己长期在用的小习惯:每个迭代结束的时候,收集那些模型表现不稳定的case,整理成一个“黑名单测试集”。名字虽然叫黑名单,但实际上不是用来惩罚模型的,而是每轮迭代必跑,以确保模型的修复方向不跑偏。用了这个方法之后,我在Agent开发里的回归问题少了非常多,这是我最愿意分享的一条实战建议。
