
先问一个挺扎心的问题你们仓库里的单元测试覆盖率是不是长期停在 30% 以下不少项目不是不想写测试而是排期永远不够、业务代码永远在变、老代码又没有测试保护改一个字段可能带出一串回归。于是过去两年我一直在琢磨一件事能否让大模型来写单元测试结果“直接让 AI 写测试”这条路试下来十次里有八次要返工。真正能稳定产出可用单测的是一条多步提示语驱动的流水线先让模型读懂代码再让它设计测试策略最后才动手生成测试代码。这就是这篇博文想详细拆解的东西——AI 自动化测试里的单测生成为什么必须“多步走”每一步的提示语该怎么设计以及我在实操中踩过的各种坑。搞清这个思路之后你会发现写单测这件事其实和带新人做需求很像你不能丢给新人一个函数就让他马上交测试用例而是要先讲需求背景、再讨论覆盖范围、再列出测试点最后才允许他写代码。多步提示语驱动的单元测试生成本质就是把“带新人写单测”的这套方法用结构化的提示词工作流复刻出来。1. 为什么“一句话让 AI 写测试”总是翻车在分享完整流程之前我想先说清楚一个问题为什么不能直接复制一个函数丢给大模型然后说“给我生成测试”很多刚上手的朋友会这么做结果生成出来的测试要么跑不过要么跑过了但等于没跑。这里面的原因值得展开讲。1.1 单次提示语的三个致命问题第一个问题是上下文严重不完整。一个函数能正常工作依赖的不只是函数本身还包括它所在的类、调用的外部接口、数据结构的约定、异常处理的规则。直接把一个孤零零的函数丢给模型它只能“看着签名猜业务”猜出来的测试大概率是基于臆想而不是基于真实行为。比如一个订单金额计算函数真正决定逻辑的是折扣规则怎么定义、商品库存不足时怎么处理、运费是否参与优惠计算——这些信息往往散落在其他类、配置文件和调用方里单次提示语根本装不下。第二个问题是断言质量极差。你翻看 AI 直接生成的测试最常见的产物是“调用了一下函数然后断言返回结果不为空”。这种断言听起来是在测实际上什么都测不出来。原因很简单模型没有被告知这个函数的“正确行为”是什么它就倾向于生成不犯错的安全断言——而安全断言恰恰是最没价值的断言。测试的意义在于验证“行为符合预期”连预期都不明确的测试只能算语法检查。第三个问题是编译和运行环境不匹配。单次生成时模型需要同时解决“理解业务”“设计用例”“写对框架语法”“处理依赖注入”“避开真实网络调用”这一堆问题。人的短期工作记忆都有容量上限大模型的上下文注意力同样会分散。当任务太多太杂生成的代码就会在某个环节出问题最常见的翻车方式是使用了项目里根本不存在的测试工具类、mock 方式写错、import 路径对不上甚至生成出同一个函数里既用了 unittest 风格又用了 pytest 风格的四不像。1.2 一步式生成违背了写测试的基本逻辑顺着上面三个问题往下挖你会发现一个更本质的原因单步生成这个动作本身违背了测试设计的基本逻辑。人写单测天然是一个“先理解后设计再编码”的过程。第一步你会读源码找出分支条件和边界点第二步你会想“这种场景该不该覆盖”“那种异常要不要模拟”第三步才动手写测试方法和断言。这三个环节心智负担完全不同而且必须串行推进。单步提示语把这些环节强行压缩成一步等于要求大模型在缺乏决策依据的情况下一次性输出最终答案。我用一个生活化类比来帮助理解你让一个厨师直接做一桌菜但不告诉他是家宴还是宴席、客人口味偏甜还是偏辣、预算多少、有几道菜必须用当天的新鲜食材。厨师也能做出来但你大概率只能得到一桌“看起来还行但完全不匹配需求”的菜。多步提示语做的就是把“明确需求”这件事单拎出来先让厨师告诉你他理解的需求菜单你确认无误后他才进入配菜和烹饪环节。2. 多步提示语的完整工作流设计既然一步式生成问题这么明显那多步方案具体长什么样我现在的标准做法是一条五步流水线每一步有明确的输入、输出和评判标准。2.1 五步流水线总览我把整个流程拆成五个阶段代码理解与信息抽取测试策略设计测试骨架生成断言与用例补全集成验证与修复听起来步骤多但每一步的目标都非常聚焦。第 1 步解决的是“模型是否读懂了代码”第 2 步解决的是“要测什么、要覆盖哪些点”第 3 步解决的是“测试文件的结构和风格”第 4 步解决的是“每个用例里最有价值的断言”第 5 步解决的是“生成的测试能不能在真实环境跑通”。每一步的输出都会作为下一步的输入这样信息是一步步累积和校验的而不是一次性灌输给模型。2.2 每步的输入输出与设计逻辑先看第 1 步“代码理解”。输入是被测类的完整源码以及必要的外部依赖说明输出不是测试代码而是 200 字以内的业务逻辑摘要。这一步的目标有两个一是逼迫模型把源码读透二是让后续步骤的对话上下文中始终带着一份已经被验证过的“代码说明书”。模型如果在这步就已经理解错了业务规则后面生成的测试必然全错所以这步的输出必须由人快速审核确认没有跑偏。第 2 步“测试策略设计”的输入是第 1 步生成的摘要加上被测类的类名和方法签名输出是一份测试点列表。这份列表要写明正常流程覆盖哪些用例、异常分支怎么触发、边界值选多少、哪些外部依赖需要 mock、哪些真实依赖可以保留。这一步相当于“测试计划”是整条流水线里最关键的质量闸门。如果测试点本身设计得不全后面再好的代码生成能力也白搭。第 3 步“测试骨架生成”的输入是被测类的源码、第 2 步的测试点列表以及行业里通用的测试框架和项目约定输出是测试类的主体框架。注意这里的框架代码里只包含测试方法签名和 mock 初始化不含具体断言。把骨架和断言拆开是为了降低单步生成的工作量让模型专注于把结构写对、把依赖注入写对。第 4 步“断言与用例补全”输入的是第 3 步的骨架以及设计好的测试点输出是完整的测试方法体。这一步才真正处理“每一条测试要验证什么”。要让模型写出有价值的断言必须在提示语里给出“断言原则”不仅断言返回值还要断言交互行为、调用次数、状态变化。第 5 步“集成验证与修复”是把生成的测试放进真实项目里跑然后根据失败信息做定向修复。这一步最容易被忽略但恰恰是最体现工程能力的一步。AI 生成代码很少一遍能直接通过尤其是在处理 mock 细节和类型转换时但修复成本远低于从零生成。2.3 为什么“策略先行”是核心整条流水线里我最想强调的就是第 2 步。很多人做 AI 辅助测试时把精力都花在打磨“请生成测试”这句话上却忘了先让模型回答一句“你觉得该测哪些点”。等到测试跑挂了才发现不是代码能力不行而是测试点本身就缺了关键一条。打个比方第 2 步就是给 AI“划考试范围”。范围划得准模型生成的每个用例都是在考关键知识点范围划得不准模型可能花费大量篇幅去测 getter/setter 这种无意义逻辑真正的核心分支却漏成筛子。因此我始终坚持一个原则任何 AI 生成测试的工作流设计测试策略都必须发生在编码之前而且必须由人来兜底审核。3. 实战为订单服务生成完整单元测试上面聊了不少方法论接下来用一个实际的例子完整走一遍流程看看每步提示语具体长什么样、模型输出什么、我们要如何审查。我特意选了一个常见的订单系统场景方便大家代入理解。3.1 准备一个演示代码先准备一个虚构的订单服务类代码用 Python 编写测试框架默认采用 pytest。这个类的逻辑不复杂但有足够多的分支适合展示测试设计时需要考量的要素。class OrderService: def __init__(self, product_repo, discount_policy, shipping_fee_calculator): self.product_repo product_repo self.discount_policy discount_policy self.shipping_fee_calculator shipping_fee_calculator def calculate_total(self, order): subtotal 0.0 for item in order[items]: product self.product_repo.get_product(item[product_id]) if not product: raise ValueError(fproduct not found: {item[product_id]}) if item[quantity] 0: raise ValueError(quantity must be positive) subtotal product[price] * item[quantity] discount self.discount_policy.compute_discount(order.get(coupon_code), subtotal) shipping_fee self.shipping_fee_calculator.calculate(order, subtotal) return max(0, subtotal - discount) shipping_fee这段代码里有几类典型的测试要点正常计算路径、商品不存在时抛异常、数量非法时抛异常、折扣计算的结果参与扣减、最终结果不为负、运费在折扣后叠加。外部依赖有三个分别是商品仓库、折扣策略、运费计算器都属于需要 mock 的对象。3.2 第 1 步代码理解提示词与输出审核第 1 步的提示语不需要写太长核心是要求模型输出可审核的摘要。下面是我常用的模板你是资深测试开发工程师。请阅读下面的 OrderService 源码输出三段内容这个类在业务流程中承担什么职责calculate_total 方法的完整执行步骤包括每个分支条件列出方法中可测试的关键行为点。 请用 200 字以内说明不要写测试代码。把源码贴进提示语后模型给出的摘要大致如下OrderService 负责计算订单最终应付金额。calculate_total 方法按行遍历订单条目逐条校验商品是否存在以及数量合法性然后累计小计接着通过折扣策略计算优惠金额通过运费计算器得到运费最终返回“小计扣减折扣后加运费”的结果且结果低于 0 时取 0。这里我要审核的点是模型是否正确理解“如果商品不存在需要抛出异常”、“折扣和运费都依赖外部组件”。如果摘要与代码不符我会直接纠正后再进入下一步绝不带着错误理解往下走。3.3 第 2 步测试策略设计提示词确认摘要无误后让模型输出测试点列表。提示语如下基于以下摘要和被测类设计完整的单元测试策略必须覆盖正常流程、异常分支、边界情况列出至少 8 个具体测试场景每个场景写明测试目标、输入数据、期望行为、需要 mock 的依赖对数量为 0、负数量、商品缺失、折扣超过小计、运费为 0 等边界情况给出专门场景只输出测试策略不写测试代码。模型会产出一张结构化的测试点表格。让我印象比较深刻的一次输出里模型自动补了我没特别强调的“空订单列表”场景。这是多步流程的价值体现当任务被拆开模型更有可能发挥它见过的测试设计经验而不是在生成代码的压力下“求稳求快”。对这一步的输出我会再复核三件事是否所有 if 分支都有对应场景、外部依赖是否都有对应的 mock 方案、是否包含“期望最终金额不低于 0”这类结果约束。缺了就当场让模型补充直到列表完整。3.4 第 3、4 步从骨架到断言的拆解在第 3 步我会要求模型先搭建测试类骨架包括 fixture 定义和各测试方法签名。提示语重点强调不要写断言避免它思维发散。请根据测试策略生成 pytest 测试类骨架要求使用 unittest.mock 创建三个 mock 对象mock_product_repo、mock_discount_policy、mock_shipping_fee_calculator每个测试方法一个独立 def方法体只写 mock 配置和被测类实例化不写业务断言先输出完整骨架代码。这里有个细节值得分享让模型统一用 unittest.mock 而不是任意 mock 库是为了减少框架适配问题。同时把 mock 配置和断言分两步生成出的代码结构会清晰很多后续排查时也容易定位是“配置写错”还是“断言写错”。等骨架确认无误再进入第 4 步。这一步的提示语我会明确给出断言编写标准并要求模型逐条实现现在为骨架中的每个测试方法补全测试逻辑。请遵守以下断言规则对每个用例必须断言最终返回值如果目标方法期望某个依赖被调用必须使用 assert_called_once_with 之类的调用断言每一处should raise的异常场景必须写 pytest.raises 上下文对于边界类场景必须同时验证结果边界如不小于 0。配合测试策略表中已经明确的输入数据和期望行为模型生成的代码质量会明显上一个台阶。我拿过模型生成的完整测试执行一次大部分用例第一次就能通过而且不再出现“只断言非空”的低质量测试。3.5 第 5 步放入项目跑一遍最后一步是把生成的测试文件放进项目根目录执行pytest tests/test_order_service.py -v。第一次跑大概率会有几个用例挂掉集中在 mock 返回值配置顺序错误、期望的异常类型不匹配之类的小问题上。我见过的失败大多不是逻辑级的而是“模型猜错了项目里某个函数返回的数据结构”。这种修起来很快改一下 mock 返回值就好。有个值得提的注意点让 AI 生成的测试代码通过以后我会再做一次变异式自检。办法很简单故意把源码里的max(0, subtotal - discount) shipping_fee改成max(0, subtotal - discount)然后跑测试看测试能不能抓出这个改动。如果一个测试套件在任何代码改动后仍然全绿说明断言还不够敏感。4. 核心细节如何让 AI 写出高质量断言我在实战部分提到过断言标准但这里值得单独展开。因为大多数人觉得 AI 生成的测试“假大空”本质上就是断言设计的环节没有控制住。AI 生成断言的能力其实不弱前提是你把断言原则写进提示语里。我总结出四条实操原则基本能解决 90% 的断言质量问题。4.1 断言设计的四条实操原则第一凡是算出结果的测试必须断言最终结果。这条听起来像废话但 AI 天然偷懒它倾向于断言中间变量而不是最终结果。写提示语时我会专门加一句“每个用例必须通过被测方法的返回值来验证业务行为”经常能显著改善输出质量。第二凡是触发依赖交互的测试必须断言交互行为。很多逻辑本身不返回值比如一个发送通知的方法返回结果是 None。对这种代码最有价值的断言恰恰是mock_sender.send.assert_called_once_with(user_123, order_shipped)。在提示语里给出具体要求如果被测方法调用了 mock 依赖必须用assert_called_once_with或等价工具验证调用参数。这样生成的测试才有资格称为行为测试而不是状态测试。第三凡是异常分支必须断言异常类型和上下文。只断言raise是不完整的要写pytest.raises(ValueError, matchquantity must be positive)把异常信息一起锁定。否则将来如果有人把抛异常逻辑改到了错误位置测试照样通过起不到保护作用。第四凡是边界值场景必须验证边界两侧。比如折扣金额刚好等于小计时结果应该为 0折扣金额大于小计时结果也应该为 0。这是最容易漏掉的高价值场景在测试策略设计阶段就该点出来。4.2 mock 的边界该替的替该留的留mock 生成是 AI 写测试时另一个重灾区。模型倾向于“能 mock 就 mock”最后生成出来的测试全在验证 mock 与 mock 之间的交互真实业务逻辑反而没测到。我在提示语里会明确画一条线只有构造成本高、行为不可控、依赖真实网络或数据库的对象才应该 mock。纯计算逻辑和方法内部的对象不要 mock。换句话说product_repo、discount_policy、shipping_fee_calculator 这类外部组件必须 mock但被测类内部自己构造的临时变量、私有方法不应该 mock。mock 边界画得太宽测试就变成了“测试 mock 框架”画得太窄测试又会因为环境因素变得不稳定。关键是先把这条原则在提示语里定死模型才不会随心所欲。刚开始我吃过的亏是模型把product[price]这种数据对象也 mock 掉了最后整个测试断言完全和真实数据脱节。后来我在提示语里补充了一句“所有直接来源于输入数据的计算不得进行 mock”输出质量立刻回升。5. 实测踩过的坑与排查手册用多步提示语跑了几十个项目之后我整理了一些高频故障模式。这里把典型问题、原因和修复手段列出来直接给大家做速查表。5.1 典型问题速查表现象常见原因修复手段生成的测试大量失败第 1 步代码理解跑偏模型对业务逻辑的摘要就是错的返回第 1 步人工纠正摘要后重新生成测试全部通过但改了源码仍通过断言力度不足只验证了非空或类型强化第 4 步提示语中的断言原则补调用断言同一个方法重复初始化 mock骨架阶段模型没有总结 fixture让模型先审查骨架把公共 mock 提取到 fixture 方法mock 返回的数据结构不符合真实代码模型没有见过真实数据结构在提示语里补充被测类的调用示例或数据字典样例测试运行缓慢且有真实网络请求mock 边界设置过窄检查策略表中所有外部依赖是否都声明了 mock 方案这五个问题是出现频率最高的。其中“测试全部通过但改了源码仍通过”最危险因为它会给团队带来虚假的安全感。我建议任何 AI 生成并已通过的测试套件都必须做一次源码变异测试把关键返回值改动一下确认测试真的能“抓住”错误。5.2 三步修复法别在生成的代码上头疼医头遇到 AI 生成的测试有问题新手容易直接在最终代码上改来改去。改完一个断言又冒出一个 import 错误越改越乱。我的经验是不要陷入细节回到流水线的上游去修复。第一步定位问题源头。先问自己这是策略问题还是生成质量问题策略问题指的是测试点本身就缺了关键场景生成质量问题指的是策略没错但代码写错了。区分方式很简单——你作为人能不能清晰说出这条用例想验证什么说不出就不可能让模型说得出去重写测试策略。第二步优化提示语而非逐个手改。如果是 assert 方式不符合项目约定就在第 4 步提示语里补充约定如果是 mock 粒度不对就在第 2 步策略里修正。批量修复的效率远高于逐行手改。第三步从小批量试运行开始。别一次性让模型生成整个测试套件先让它生成 2 到 3 个场景跑通后再逐渐放大到所有测试点。按这个节奏来绝大多数翻车都能在早期被拦住。5.3 覆盖率之外更值得关注的“测试有效性”最后想聊一个容易被忽视的话题覆盖率数据。很多人习惯用行覆盖率来衡量 AI 生成测试的价值但在实践中你会发现AI 生成测试的行覆盖率可以轻松做到 80% 以上因为模型会刻意调用代码里的每一行但这并不等于这些测试都验证了正确行为。覆盖率是一只猿也促进驱动的数字无法体现“断言是否足够苛刻”。我更建议用两个指标来评估第一个是“变异测试通过率”即在引入若干处故意缺陷后测试能否失败第二个是“失败用例可读性”即当测试真的失败时报错信息能不能让人一眼看出是哪个业务行为不符合预期。这两个指标能真正反映 AI 生成测试对项目的保护能力。从我个人的经验来看多步提示语驱动这套流程最大的收益还不是单纯的“单测生成成功率”而是它把整个测试设计过程变成了一种可以评审、可以优化、可以积累的工程资产。第 2 步产出的测试策略表不仅 AI 会用团队里的新人也能拿来做业务逻辑的快速理解。这个意外收获反而比多生成几个测试文件更让我满意。如果你也想在团队里落地 AI 单测生成建议不要直接追求一步到位的“全自动”而是先通过多步流水线把“测试策略设计”这个环节固化下来。这一步做扎实了后面的生成和修复都会顺理成章。