说实话,干了快十年的自动化测试,我从来没想过自己会有一天主动把写用例这个活儿交给AI——但这部分工作恰恰是过去几年最让我头疼的环节。需求文档越来越长,业务逻辑越来越绕,用例覆盖既要照顾正常路径,又要在边界条件里挑刺,每个版本迭代都要重写一遍测试用例。直到我把LLM引入自动化测试流程,用大模型辅助生成测试用例,才发现这套思路不仅可行,而且真的能解决一直困扰团队的用例覆盖面不足、维护成本高、执行结果不稳这三座大山。这篇文章,我把从选型、设计Prompt、搭RAG到跑通pytest/Playwright的完整过程,以及踩过的坑,一条条写清楚。
如果你也是自动化测试工程师、测试开发,或者正在为接口自动化测试框架、UI自动化测试脚本的设计头痛,希望从“手写用例”切换到“AI生成+人工审核”的新工作方式,那这篇文章应该能帮你节省大量试错时间。
1. 先聊聊传统测试用例生成到底卡在哪
1.1 手工写用例的三个死穴
先说一个我观察了很久的现象:很多测试团队的用例覆盖率其实很低,但大家意识不到。为什么?因为黑盒测试用例的设计严重依赖个人经验和想象力。经验丰富的老测试知道要测重复支付、余额不足、并发抢购、幂等性,新来的测试往往只写了正常支付和支付失败两三条就交差。这种“覆盖靠个人经验、经验又不沉淀”的模式,是手工写用例的第一个死穴。
第二个死穴是需求文档和测试用例严重脱节。产品文档更新了一版,接口字段改名了,但测试用例没人同步修改。等到提测那天,测试团队拿着过时的用例去测新功能,结果全是对不上的报错。本质上,手工维护测试用例就是一个文档同步的体力活,文档更新得越频繁,同步成本就越高。
第三个死穴是变更响应慢。一次需求迭代,往往涉及接口参数调整、前端页面改版、状态流转变化。手工模式下,修改一个用例可能要连带修改测试数据、断言逻辑、前置条件,改完还要逐个检查受影响的其他用例。这个成本在小项目里不明显,一旦用例数量上千,变更就是一场灾难。算一笔账:假设一条接口用例平均维护成本是30分钟,1000条用例就是500小时,这还没算执行失败后的排查时间。
1.2 LLM切入测试领域的三个机会点
问题摆在那,LLM能解决什么?我总结下来有三个非常明确的机会点。
第一个是自然语言到结构化测试用例的自动映射。LLM最基础也最强大的能力就是理解自然语言。你给它一段需求描述,它能提取出功能点、业务规则、异常分支,然后转换成结构化测试用例。这相当于把“人脑经验驱动”的测试设计,变成了“模型推理驱动”,新人也能直接获得接近老测试水平的用例设计能力。
第二个是从测试点到可执行代码的自动翻译。传统流程里,测试人员设计完测试点,还得手动写HTTP请求、写断言、写UI操作步骤。LLM可以把“测试用例描述”直接翻译成pytest用例、requests调用、Playwright脚本,一步到位。这里的价值不只是省代码量,而是让写代码的门槛大幅下降——非技术背景的测试人员也能通过自然语言生成可执行的自动化脚本。
第三个是探索式测试的自动化。这在传统框架里几乎做不到,但LLM Agent可以自动打开页面、分析元素、尝试各种输入组合、观察系统反应,发现异常后自动生成缺陷报告。这个方向还处于早期阶段,但值得关注。
2. LLM生成测试用例的核心技术路线
2.1 LLM Agent:从“生成一段文字”升级到“跑完一个任务”
最初我用LLM生成测试用例,就是简单地把需求描述丢给模型,让它返回一批用例文本。这种方式有一个明显问题:模型只是在“编故事”,它不知道系统的真实行为,也没有验证能力。生成出来的用例看起来很合理,但存在很多一执行就挂的假用例。
后来我换成了LLM Agent的思路。所谓Agent,本质上是一个循环:LLM推理→决策→调用工具→观察结果→再次推理。在测试场景里,Agent可以调用接口工具发送真实请求,调用浏览器工具操作页面元素,调用数据库工具查询测试数据。这样生成一个用例后,Agent不是直接输出,而是先验证一遍这个用例能否跑通,再决定保留还是调整。
举个例子,我想测一个注册功能。传统方式是手动读需求文档写测试用例;直接用LLM是让模型根据注册功能描述生成用例;用Agent则是让模型自己去调用注册接口,传入各类参数组合,看实际返回什么,再根据真实响应生成或修正测试用例。同样叫“生成”,后者的准确率和实用性完全不在一个量级。
2.2 RAG:让模型拿到业务上下文,而不是瞎猜
这也是我踩过的最大的坑。直接用LLM的通用知识生成用例,面对通用电商、通用管理系统还行,一碰到公司内部的特殊业务逻辑,模型就开始胡说八道。比如我们有个订单状态机:待支付→已支付→已发货→已完成,外加一个“已取消”,模型不知道这个流转规则,生成的用例里就会出现“已完成→已支付”这种系统里根本不存在的非法转换。
解决方案就是RAG。把业务知识——需求文档、接口文档、历史测试用例、缺陷报告、业务字典——切分成片段,向量化后存入向量数据库。每次生成用例前,先检索与当前功能最相关的上下文片段,把它们拼进Prompt,再让LLM基于这些真实材料生成。
实践下来,值得放进知识库的内容有:接口定义文档(OpenAPI/Swagger)、数据库表结构说明、订单状态机、枚举字典、历史线上缺陷列表、已沉淀的高质量用例。这些内容对用例生成的准确性影响最大。我用Chroma做向量存储,3000个切片的检索平均时长在50毫秒以内,效果很好。
2.3 Prompt设计:决定输出质量的那20%关键因素
在LLM生成测试用例这件事上,Prompt设计的价值占了至少一半。很多人问为什么我生成的用例不专业,看了他们写的Prompt就明白了——他们写的是“帮我想几个测试用例”,这种模糊指令只能得到模糊结果。
我的标准Prompt分四层。第一层是角色设定,让模型进入“资深测试架构师”的状态;第二层是任务规则,明确用例必须包含哪些字段、覆盖哪些路径、禁止编造数据;第三层是输出格式,指定JSON结构或Markdown表格,方便后续解析;第四层是Few-shot示例,给一个或两个标准的优秀用例做参照。
我实际用的System Prompt大概是这样的:
你是一位资深测试架构师,具备8年以上接口自动化和UI自动化经验。 你的任务是根据用户提供的功能描述生成测试用例。 必须遵守的规则: 1. 每条用例必须包含:用例ID、前置条件、操作步骤、输入数据、预期结果、优先级。 2. 必须覆盖正常路径、异常路径、边界条件三类场景。 3. 输入数据只能使用你掌握的真实业务字典,不确定的字段用null表示,禁止编造。 4. 输出必须是合法的JSON数组,不要输出任何解释性文字。配合这个System Prompt,User端只安心描述功能即可,生成质量会大幅提升。关键点有两个:用规则约束模型的行为边界,用格式约束保证输出的可解析性。
3. 从零搭建一套LLM测试用例生成工作流
3.1 先定边界:LLM负责什么,测试框架负责什么
很多团队一上来就想做“全自动测试”,让AI完全替代人,结果往往翻车。我建议先划清边界:LLM负责生成测试用例设计和代码骨架,pytest/Playwright/Appium这些传统测试框架负责执行和断言,测试工程师负责审核和兜底。
为什么要这样切?因为LLM生成的代码不能保证100%可运行,断言逻辑也可能存在偏差。如果让LLM直接驱动执行,一旦出错就是连锁反应,排查成本极高。反过来,只让LLM做“设计层”的工作——生成测试点、生成代码模板、生成测试数据——再由人在测试框架里审核,风险就完全可控了。
3.2 工具选型清单与对比
这里的选型方案我已经在多个项目中验证过,按环境需求做了两套组合。
| 环节 | 在线API方案 | 本地私有化方案 | 选择理由 |
|---|---|---|---|
| LLM推理 | OpenAI/国产大模型API | vLLM部署Qwen2.5-72B | 数据敏感选本地,追求效果选API |
| Agent编排 | LangChain 或 原生Function Calling | LangChain | 灵活度更高,改造成本低 |
| 接口测试执行 | pytest + requests | 同上 | pytest生态成熟,断言灵活 |
| UI自动化执行 | Playwright | Playwright | 定位器策略优于Selenium,异步性能好 |
| 移动端自动化 | Appium | Appium | 跨端支持,社区活跃 |
| 向量知识库 | Chroma | Milvus | 小规模Chroma够用,大规模选Milvus |
特别说一下UI自动化框架的选择。以前我用Selenium多一些,但和LLM配合后明显感觉到Playwright更顺手。原因在于Playwright的定位器是声明式的,支持角色定位、文本定位、层级定位组合,LLM生成这类定位策略的准确率远高于生成复杂的XPath表达式。如果LLM写一条XPath,十次可能有三次定位不到;但让它写“page.getByRole('button', name='提交')”这种语义化定位,准确率就高很多。移动端依然是Appium,没有替代品。
3.3 模型部署时的精度选择经验
如果你选择本地私有化部署模型,会碰到一个热搜词里反复出现的概念:精度问题,也就是fp16、fp32、bf16怎么选。我用vLLM部署7B和72B模型时做过对比,这里直接给结论。
fp16的问题在于数值范围小,某些激活值容易溢出,生成质量的波动比较明显;bf16牺牲了一部分尾数精度,但数值范围和fp32相当,在推理场景下几乎无损,是目前最平衡的选择;fp32质量最稳,但显存占用翻倍。我的实际配置是7B模型用bf16,显存占用大约14GB,生成的测试用例输出稳定,没有遇到数值异常。72B模型也用bf16加载,需要大约144GB显存,此时精度影响依然可控。如果你的显存只有16GB左右,又必须跑7B模型,可以退一步用fp16加AWQ量化,质量略有下降但能跑得动。
启动一个OpenAI兼容接口的本地模型服务,命令如下:
python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-7B-Instruct \ --dtype bfloat16 \ --gpu-memory-utilization 0.9 \ --served-model-name qwen启动后服务会监听8000端口,接口格式和OpenAI一致,代码里只要改base_url就能无缝切换。
3.4 一个可直接跑的Python实现
我写了一个精简版但完整的实现,核心逻辑是:传入功能描述和可选的业务上下文,返回结构化测试用例JSON。这是整套工作流里最核心的模块。
from openai import OpenAI import json DEFAULT_SYSTEM_PROMPT = """你是一位资深测试架构师,具备8年以上接口自动化和UI自动化经验。 你的任务是根据用户提供的功能描述生成测试用例。 必须遵守的规则: 1. 每条用例必须包含:用例ID、前置条件、操作步骤、输入数据、预期结果、优先级。 2. 必须覆盖正常路径、异常路径、边界条件三类场景。 3. 输入数据只能使用你掌握的真实业务字典,不确定的字段用null表示,禁止编造。 4. 输出必须是合法的JSON数组,不要输出任何解释性文字。""" def generate_api_test_cases(description: str, context_docs: str = None): client = OpenAI( base_url="http://localhost:8000/v1", # 本地vLLM api_key="EMPTY" # 或换成你的云API Key ) messages = [ {"role": "system", "content": DEFAULT_SYSTEM_PROMPT}, ] # 注入RAG检索到的业务上下文 if context_docs: messages.append({ "role": "user", "content": f"以下是相关业务上下文,生成用例时必须参考:\n{context_docs}" }) messages.append({ "role": "user", "content": f"请为以下功能生成接口测试用例:\n{description}" }) resp = client.chat.completions.create( model="qwen", temperature=0.2, response_format={"type": "json_object"}, messages=messages, ) return json.loads(resp.choices[0].message.content) # 测试:注册功能 feature_desc = """ 用户注册功能: 1. 手机号作为账号,必须是11位,且以1开头。 2. 需要短信验证码,验证码有效期5分钟。 3. 密码要求8-20位,必须同时包含字母和数字。 4. 同一手机号只能注册一次。 """ cases = generate_api_test_cases(feature_desc) print(json.dumps(cases, ensure_ascii=False, indent=2))输出的用例大概长这样:
[ { "case_id": "TC_REG_001", "priority": "P0", "category": "正常路径", "precondition": "数据库不存在手机号13800138000", "steps": [ "发送验证码请求至 /api/auth/captcha,参数 mobile=13800138000", "验证码接口返回成功", "提交注册请求至 /api/auth/register,参数 mobile=13800138000, captcha=123456, password=abc12345", "校验响应码为200,响应体包含 token" ], "expected": "注册成功,返回token,数据库新增用户记录" }, { "case_id": "TC_REG_101", "priority": "P1", "category": "异常路径", "precondition": "手机号13800138001已注册", "steps": [ "提交注册请求,参数 mobile=13800138001, captcha=123456, password=abc12345", "校验响应码" ], "expected": "返回400,错误码 PHONE_EXISTS,提示手机号已注册" } ]有了JSON用例,接下来就是把它翻译成pytest执行用例。我的做法是再用一次LLM调用,把JSON转化成pytest代码。这一步可以批量做,一次生成全部用例,再人工审核后合入测试仓库。
def case_json_to_pytest(cases_json: str) -> str: resp = client.chat.completions.create( model="qwen", temperature=0.1, messages=[ {"role": "system", "content": "你是一名Python测试开发工程师,请将测试用例JSON转换为可直接运行的pytest代码。使用requests库,断言清晰,代码添加详细注释。"}, {"role": "user", "content": cases_json} ] ) return resp.choices[0].message.content整体工作流也就四步:整理需求描述→RAG检索业务上下文→生成用例JSON→生成pytest代码并入仓库。以前一个模块的接口测试用例从编写到审核需要大半天,现在压缩到一小时以内。
4. 落地中的常见问题与排查技巧
4.1 UI自动化里最让人崩溃的非预期弹窗问题
如果你用LLM生成UI自动化测试脚本,一定绕不开一个问题:自动化跑到一半,页面突然弹出“系统升级通知”“新人优惠券”“订阅推送”之类的非预期弹窗,点击元素失败,整个用例报废。这个热搜词排行也印证了:这个问题的发生频率非常高。
弹窗的麻烦在于它出现的时机不确定,定位器稳定也没用。很多自动化初学者遇到这个问题的第一反应是加time.sleep,但这是最不推荐的做法。sleep固定等待要么等不够,要么浪费大量时间。
我的方案是在Playwright里做三层保障。第一层是全局自动处理常见对话框:
# 处理原生JavaScript的alert/confirm/prompt page.on("dialog", lambda dialog: dialog.dismiss())第二层是准备一个通用的关闭弹窗函数,识别常见的弹窗选择器,发现后自动点击关闭或确定:
async def close_known_popups(page): selectors = [ "button:has-text('我知道了')", "button:has-text('关闭')", ".modal-close", "[aria-label='Close']", "#popup-close" ] for sel in selectors: locator = page.locator(sel).first if await locator.is_visible(): await locator.click(timeout=2000) await page.wait_for_timeout(200)第三层是给关键操作加自动重试机制。点击按钮前先检查是否有弹窗遮挡,如果元素不可见或不可点击,先执行关闭弹窗操作,再执行原操作。
这个问题的深层原因在于被测系统的前端实现不够规范,弹窗乱弹、没有统一管理。如果你们有前端话语权,推动组件库统一弹窗实现,比在自动化里反复修补要有效得多。
4.2 生成用例质量不稳定怎么应对
我刚开始用LLM生成用例时,发现一个很烦的现象:同一段需求,上午生成的用例质量很高,下午生成的可能就少了好几条边界条件。这本质上是LLM采样的随机性问题。
排查下来,最有效的控制手段有三个。第一个是降低temperature参数。我默认是0.2,如果生成结果偏创造性、偏离需求,就继续降到0.1。生成测试用例不是写作,我们不需要模型的灵感,只需要它稳定输出。第二个是必须加Few-shot示例。在Prompt里给出两三个高质量的参考用例,模型的输出会迅速向示例风格靠拢,这在OpenAI和开源模型上都有效。第三个是对生成结果做schema级校验,用JSON Schema校验字段是否齐全、枚举值是否合法,不通过就让模型重新生成一次。有了这三层控制,生成质量基本能稳定住。
另一个问题是LLM不熟悉业务,容易编造数据。之前我用它生成订单接口用例,模型生成了“status=COMPLETED_UNKNOW”这种后端根本不存在的枚举值。这个问题的最优解还是RAG。把业务字典、枚举类型、字段注释全部灌进知识库,Prompt里明确要求“只能使用参考上下文中出现的枚举值”,编造率能下降80%以上。
4.3 Token成本和上下文长度控制
生成测试用例看起来只是发几个Prompt,但实际跑起来成本并不低。一个大型接口模块的需求文档可能有5000字,接口定义有3000字,全部塞进Prompt,一次调用就要消耗几千个Token,加一个模块的用例,费用会非常可观。
我的做法是按需裁剪,绝不全量输入。需求文档只提取和该功能直接相关的段落,接口定义通过解析OpenAPI的path和schema来精确定位,而不是整篇发送。RAG检索出来的上下文也做截断,限制在1000字以内。最终Prompt控制在1300字左右,生成的用例JSON控制在2000字以内。
另一个省钱技巧是批量生成。同一个模块下的多个功能点,不要在循环里逐个调模型,而是把功能列表拼在一起,让模型一次生成一个模块的完整用例集。减少调用次数,成本和耗时都成倍下降。实测下来,单接口模块的用例生成成本能压到几块钱人民币,和人工成本相比基本可以忽略。
5. 团队落地实践:从试点到稳定的三条经验
5.1 第一刀切在接口层,而不是UI层
如果团队要引入LLM生成测试用例,我强烈建议先做接口自动化,不要一上来就碰UI自动化。原因很现实:接口测试的输入输出是结构化的,请求参数、响应码、断言逻辑都很明确,LLM生成的代码准确率更高,出错了也容易排查。UI自动化涉及定位器、页面加载时机、弹窗处理、跨域跳转,变量太多,LLM生成的脚本一次性通过率会低很多。
我们团队当初选了订单查询接口作为试点,两周内就上线了第一批AI生成的接口测试用例,通过率大约85%,剩下的15%经过人工调整后也能正常跑通。这个成绩给了团队信心,后面再推UI自动化时阻力就小得多。
5.2 建立生成用例的质量评估闭环
AI生成的用例不能直接上线,必须建立质量评估体系。我的做法是三个指标:代码可编译率、执行通过率、业务规则覆盖率。每次生成一批用例后,先跑一遍静态编译,再在测试环境执行,看通过情况,最后对照需求文档检查关键业务规则是否都有对应用例。
这三个指标的记录和分析一定要自动化。我写了一个简单的统计脚本,每次用例生成后自动生成报告,推送到团队群。有了数据支撑,团队才能持续优化Prompt和知识库,而不是靠感觉调参数。
5.3 测试工程师的新角色:审核者与知识库维护者
引入LLM后,测试工程师的角色在悄悄变化。过去我们大部分精力在写用例、调试脚本,现在这些工作被AI承担了大部分,剩下的核心工作是审核AI生成的内容是否正确。这要求测试工程师对业务有更深的理解,因为只有真正懂业务,才能判断AI生成的用例是否有遗漏、断言是否符合预期。
另一个新职责是维护知识库。这是很多人忽略的点——知识库的质量直接决定AI生成用例的质量。业务字典更新了、接口字段改变了,这些信息必须同步进入知识库。我们团队现在每周有一个固定流程,出现新需求或字段变更后,先更新知识库,再让AI生成用例。这个流程建立起来后,整个测试用例生成链路才真正稳定运转。
就我个人的使用体验来说,LLM不是来抢饭碗的,它更像一个不知疲倦的刚入职测试开发,底子不错但不懂业务。你要做的是给它足够的业务知识,制定清晰的规则,最后严格把关。用好了,它能把我们从重复的用例编写中解放出来,把精力放到更需要判断力的测试设计上。这条路我已经走通了一半,剩下的,还需要更多实践来完善。