用大模型生成单元测试时,最常遇到的问题不是模型能力不够,而是提示词没有把测试所需的工程上下文交代清楚。只给一个函数名或一小段代码,模型通常会返回几个覆盖主流程的断言,但边界条件、异常分支和Mock配置经常缺失,生成的测试无法直接运行。要让模型产出能直接落入项目的测试用例,提示词需要像一份小型的测试设计说明书,把被测对象、依赖关系、断言风格和禁止事项一次性说透。

一、提示词必须包含的上下文要素
单元测试生成不是翻译任务,模型需要知道的不只是代码长什么样,还包括代码在项目里如何被调用、哪些依赖需要隔离、测试框架用的是什么风格。上下文要素可以拆成四块:被测签名、依赖关系、框架约定、验收标准。
第一块是被测签名。除了给出函数或类的完整代码,还要说明参数类型、返回类型以及可能抛出的异常。很多开发者在提示词里只贴一段方法体,却省略了构造器签名和字段类型,结果模型只能用猜测的方式生成测试,参数传错的情况非常普遍。第二块是依赖关系。如果被测类依赖了数据库连接、HTTP客户端或第三方服务,需要明确告诉模型哪些依赖应该用Mock替代,以及Mock工具是Jest、Mockito还是unittest.mock。
第三块是框架约定。不同项目的测试框架差异很大,有的用pytest的assert语句,有的用JUnit 5的assertEquals,还有的用Jest的expect链式断言。提示词里如果不指定框架和命名规则,模型生成的测试可能混用多种风格,维护起来很痛苦。第四块是验收标准,也就是告诉模型怎样算合格的测试,比如至少覆盖正常路径、空值输入和异常路径,断言必须验证返回值而不仅仅是“不抛异常”。
下面是一个最小上下文的提示词片段,原始提示词可以直接复制调整:
# 提示词模板:为 Python 函数生成单元测试
被测函数:
def calculate_discount(price: float, member_level: str) -> float:
if price < 0:
raise ValueError("price must be non-negative")
if member_level == "gold":
return price * 0.7
if member_level == "silver":
return price * 0.85
return price
测试框架:pytest
Mock要求:无外部依赖,不需要Mock
必须覆盖的用例:正常价格、0价格、负价格抛异常、三种会员等级
断言风格:使用 assert,验证具体数值
注意提示词中的函数代码块要与自然语言描述分开,避免模型把注释当成指令忽略。如果依赖了外部模块,还需要把导入语句和类构造器一并贴进去。
二、高质量提示词的模板设计
一个完整的提示词模板通常会包含角色设定、任务目标、输入信息、输出格式和负向约束。角色设定能帮助模型切换到测试工程师视角,生成结果更偏向工程可维护性而不是教学示例。任务目标要具体,比如“为以下Python类生成pytest测试文件,覆盖所有public方法,包含正常和异常路径”,避免只说“写测试”。
输入信息需要结构化,建议使用分隔线或标题把“被测代码”“依赖说明”“测试要求”分开。输出格式也要明确,例如要求模型只输出测试代码块、不要输出解释性文字,并且文件命名遵循test_*.py。负向约束同样重要,可以写“不要使用不存在的API”“不要生成预期异常的冗余try-except”“每个测试方法只验证一个行为”。
下面是一个可直接使用的Markdown风格提示词模板,语言类型标记为text:
你是一名资深测试工程师,负责为现有Python项目编写单元测试。 被测代码: <paste code here> 依赖说明: - 模块依赖:requests - 需要Mock的依赖:requests.get - Mock框架:unittest.mock.patch 测试要求: 1. 使用pytest框架 2. 每个public方法至少包含:正常输入、空值、边界值、异常路径 3. 断言必须验证返回值内容和函数调用次数 4. 输出完整的test_*.py文件,不要输出解释性文字 5. 禁止使用不存在的属性或方法,所有Mock必须基于真实依赖签名
模板中的<paste code here>在正文展示时使用了转义,实际填写时替换为真实代码即可。该模板重点在于把“需要Mock的依赖”单独列出来,能显著减少模型凭空构造API的情况。如果项目使用TypeScript和Jest,只需要把框架和Mock部分替换成jest.mock即可,结构保持不变。
此外,可以在提示词中加入示例测试作为few-shot。比如给一个简单的函数和对应测试,让模型模仿命名风格和断言组织方式。示例不需要很长,两三个测试方法就足够锁定输出格式。
三、避免生成无效测试的优化技巧
大模型生成单元测试最常见的失败模式有三种:只覆盖主流程、幻觉依赖API、断言形式化。针对这三种情况,可以在提示词中加入针对性的负向指令和补充信息。
只覆盖主流程通常是因为提示词没有明确边界条件。改进方法是列出必须覆盖的输入类型,而不是只写“覆盖边界”。例如直接写“必须测试空字符串、None、0、负数、极大值”比写“考虑边界情况”有效得多。如果你能提供生产代码中的参数校验逻辑,模型更容易推断出应该验证哪些异常。
幻觉依赖API往往发生在被测代码依赖了第三方库,但提示词没有给出依赖的真实接口。模型会根据库名猜测方法,例如对requests.get返回的对象调用.json()没问题,但如果给出的是自定义的HTTP客户端,模型可能会编造client.fetch_json()这样的方法。解决方法是在提示词中要求模型“只使用被测代码中出现的依赖方法,未知方法一律不要假设”,并贴出依赖类的关键方法签名。
断言形式化指模型生成的测试只看“执行不报错”,没有验证业务结果。例如对计算函数只写assert result is not None,这样的测试几乎没有价值。提示词可以要求“每个断言必须验证具体的返回值或状态变化,禁止只断言非空或布尔真假”。同时可以加入一个反例,让模型知道什么样的断言不合格。
# 不合格断言示例(不要生成类似代码)
def test_calculate():
result = calculate_discount(100, "gold")
assert result # 这只验证了truthy,不是数值
在编写提示词时,建议采用“正向要求+负向示例”的组合。正向要求给出必须覆盖的行为,负向示例用一小段不合格代码告诉模型不要做什么。这比反复强调“生成高质量测试”更直接。
四、从生成到可维护的工程化实践
提示词写好之后,还需要把生成结果接入工程流程,否则测试文件只是临时产物。一个比较实用的做法是把提示词模板保存为项目文档,配合脚本批量生成测试骨架。比如在代码评审前,用大模型对新增模块批量生成初版测试,再由开发者补充复杂业务断言。
在持续集成环境中,可以将生成测试作为辅助步骤:先用模型生成候选测试文件,执行pytest --cov或jest --coverage检查覆盖率,如果某个分支没有被覆盖,再把覆盖报告回传到提示词中,要求补齐缺失的分支。这种闭环方式能逐步提升生成测试的完整度。
另外,生成测试的维护成本也需要控制。如果被测接口发生变化,旧的测试可能大量失败。因此提示词中可以要求模型在测试文件头部生成一段注释,标明测试基于哪个版本的接口,并避免依赖实现细节。对Mock的使用也要克制,不要把所有依赖都Mock掉,否则测试只能验证调用存在,无法发现集成问题。
最后提醒一点:大模型生成的单元测试仍然需要人工审查,尤其是涉及安全、金额、并发等关键逻辑的测试,不能直接信任模型生成的断言。把大模型定位为测试草稿生成器,把提示词当作规范化输入,既能提高效率,也能保持项目质量。