AI测试系统系列会写28篇,这是第3篇,会持续更新
AI生成的测试用例太"水"?14年老兵:规则引擎+AI才是王炸组合
第 2 篇的规则引擎 10 毫秒出 36 条用例,但说实话,步骤描述都是"进入功能页面 → 执行正常操作流程 → 提交操作"这种万能模板。测试工程师拿到手还得手动细化,否则根本没法执行。
说实话,第一次看到 AI 生成的用例我差点把咖啡喷出来------这哪是测试用例,这是废话模板。
AI Skill 要解决的就是这个问题。把需求丢给大模型,让它像真正的测试工程师一样思考:这个功能有哪些正常场景?哪些异常场景?边界值在哪里?数据怎么构造?
为什么不直接用 AI 生成所有用例?
很多人第一反应是:规则引擎出的用例这么水,直接用 AI 不就行了?
实际上不行,原因有三个:
- 成本:一个需求文档拆成 20-50 个功能点,每个都调 AI,一次 0.05-0.1 元,就是 1-5 元。企业级项目上千个需求,成本不小。
- 速度:AI 调用 5-30 秒,规则引擎 10 毫秒。用户点击"生成"后等 30 秒 vs 等 0.01 秒,体验天差地别。
- 确定性覆盖:CRUD、权限、边界值这些标准场景,规则引擎保证 100% 覆盖。AI 可能漏掉,因为它"随机"。
所以规则引擎的定位是保底 ------快、免费、稳定、覆盖标准场景。AI 的定位是锦上添花------创意、深入、覆盖边缘场景。两者配合,效率和成本都有保障。
但 AI 不是丢句话就完事。Prompt 设计得好,生成的用例可以直接用;设计得烂,生成的东西连看都不想多看一眼。经过多轮调优------特别是加了历史参考用例做风格对齐后,生成质量明显提升,分享几个关键经验。
一、Prompt 设计:五个要素缺一不可
rust
def _build_prompt(self, requirement: str, context: Optional[Dict] = None) -> str:
base_prompt = f"""
你是一个专业的测试工程师。请根据以下需求生成测试用例。
## 需求描述
{requirement}
## 历史参考用例
{json.dumps(context, ensure_ascii=False, indent=2) if context else "无"}
## 输出格式要求
请按以下 JSON 格式输出:
{{
"test_cases": [
{{
"title": "用例标题",
"description": "用例描述",
"priority": "P0/P1/P2/P3",
"type": "functional/api/performance/security",
"preconditions": ["前置条件 1", "前置条件 2"],
"steps": [
{{
"step": 1,
"action": "操作步骤",
"expected": "预期结果"
}}
],
"test_data": "测试数据",
"automation": true/false
}}
]
}}
## 生成要求
1. 覆盖正常场景和异常场景
2. 包含边界条件测试
3. 优先级合理分配(P0 核心功能,P1 重要功能,P2 一般功能,P3 边缘功能)
4. 步骤清晰可执行
"""
return base_prompt
这个 Prompt 包含了五个要素:
角色定义:"你是一个专业的测试工程师"。别小看这句话,加了和没加,生成的用例质量差很多。AI 会代入角色身份来思考问题。
需求描述:用户输入的需求文本。这是 AI 生成的素材来源。
历史参考用例:如果传入了 context(比如 RAG 检索到的历史用例),就放在这里。这招很关键------AI 看到历史用例的风格和质量后,新生成的用例会保持一致的水平。
输出格式要求:用 JSON Schema 指定输出结构。这比自然语言描述精确得多,AI 会严格按照这个结构生成内容。
生成要求:四条具体要求。没有这四条,AI 生成的用例往往只覆盖正常场景,漏掉异常和边界。
二、temperature 调参:0.7 是甜点
根据经验,temperature 不同值的效果大致如下:
| temperature | 效果 | 问题 |
|---|---|---|
| 0.3 | 用例很稳定,每次生成几乎一样 | 缺乏多样性,覆盖的场景太少 |
| 0.7 | 稳定性和多样性平衡 | 偶尔有低质量用例,需要评分过滤 |
| 1.0 | 创意十足,能想到很多边缘场景 | 质量不稳定,有时生成离谱的用例 |
最终选了 0.7。稳定性够用,多样性也够,偶尔的低质量用例用质量评分过滤掉就行。
三、质量评分:四维度打分,低于 0.7 的直接过滤
AI 生成的用例不能直接用,得先过质量评分这一关。我们的评分模型四个维度:
csharp
def _calculate_quality_score(self, test_case: Dict) -> float:
score = 0.0
# 完整性(30%):步骤数 >= 3
steps = test_case.get("steps", [])
if len(steps) >= 3:
score += 0.3
# 清晰度(30%):标题 > 5 字符且描述 > 10 字符
title = test_case.get("title", "")
description = test_case.get("description", "")
if len(title) > 5 and len(description) > 10:
score += 0.3
# 优先级(20%):P0=0.2, P1=0.15, P2=0.1, P3=0.05
priority = test_case.get("priority", "P3")
score += {"P0": 0.2, "P1": 0.15, "P2": 0.1, "P3": 0.05}.get(priority, 0.05)
# 可自动化(20%)
if test_case.get("automation", False):
score += 0.2
return min(score, 1.0)
权重分配的逻辑:步骤不全的用例没法执行(完整性最重要),描述不清楚的用例难以理解(清晰度次之),优先级和可自动化是加分项。
实际跑下来,质量分低于 0.7 的用例确实问题比较多------要么步骤太少,要么标题就写了两个字。过滤掉之后,剩下的用例基本可以直接用。
四、容错处理:LLM 不按套路出牌怎么办
大模型不是每次都乖乖返回 JSON。有时候在 JSON 前面加一句"好的,以下是生成的测试用例:",有时候在 JSON 后面加一句"希望对你有帮助"。直接 json.loads() 会报错。
我们的处理方式是用正则提取 JSON:
python
def _parse_llm_response(self, response: str) -> List[Dict]:
try:
data = json.loads(response)
return data.get("test_cases", [])
except json.JSONDecodeError:
# 从文本中提取 JSON
import re
json_match = re.search(r'{.*}', response, re.DOTALL)
if json_match:
try:
data = json.loads(json_match.group())
return data.get("test_cases", [])
except:
pass
return []
re.DOTALL 标志让 . 匹配换行符,这样多行 JSON 也能正确提取。
注意 :这个正则会匹配从第一个
{到最后一个}的内容。对大多数情况够用,但如果 LLM 返回了多个 JSON 块(比如先返回错误格式再返回正确格式),可能会匹配到无效内容。更精细的场景可以用json.loads()逐行尝试。
五、降级方案:API 挂了不能等死
python
def _call_llm(self, prompt: str, requirement: str = "") -> str:
api_key = os.getenv("DASHSCOPE_API_KEY", "")
if not api_key:
return self._get_rule_engine_response(requirement) # 没 Key,降级到规则引擎
try:
response = httpx.post(
"https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation",
headers={"Authorization": f"Bearer {api_key}", ...},
json={"model": "qwen3.5-plus", "input": {...}, "parameters": {"temperature": 0.7, "max_tokens": 2000}},
timeout=30.0
)
if response.status_code == 200:
return response.json().get("output", {}).get("text", "")
else:
return self._get_rule_engine_response(requirement) # API 报错,降级到规则引擎
except Exception:
return self._get_rule_engine_response(requirement) # 网络异常,降级到规则引擎
def _get_rule_engine_response(self, requirement: str) -> str:
"""降级方案:使用规则引擎生成用例"""
engine = self.rule_engine
if engine and requirement:
cases = engine.generate_cases_from_requirement(requirement)
if cases:
return json.dumps({"test_cases": cases}, ensure_ascii=False)
# 规则引擎也不可用时,最后兜底用 Mock
return self._get_mock_response()
三级降级:LLM → 规则引擎 → Mock 兜底。
- 第一级:正常调用 LLM,生成高质量用例
- 第二级:API 挂了/没配置 Key,调用规则引擎生成针对性用例(实测 22 条,平均质量分 0.77,快、免费、真实)
- 第三级:规则引擎也不可用(极端情况),返回硬编码 Mock 数据保证流程不崩
Mock 响应是两条登录用例的硬编码数据,只在最极端的情况下使用。正常情况下,规则引擎就能提供可用的用例。
六、成本估算:一次生成花多少钱
qwen3.5-plus 的定价大约是输入 0.02 元/千 tokens,输出 0.02 元/千 tokens。
一次用例生成大约消耗:
- 输入:Prompt(约 800 tokens)+ 需求文本(约 200-500 tokens)= 1000-1300 tokens
- 输出:5-10 条用例(约 1500-2500 tokens)
单次调用成本大约 0.05-0.1 元(具体取决于实际 token 消耗和模型定价)。
一次需求生成 10 条用例,成本约 0.05-0.1 元。比想象中便宜很多。
七、踩过的坑
坑 1:temperature 设太高。一开始设了 1.0,AI 生成的用例创意十足但质量不稳定。有时候一条用例的步骤写得像小说,测试工程师根本没法执行。降到 0.7 之后稳定多了。
坑 2:没有质量评分。第一版直接返回 AI 生成的所有用例,结果 30% 的用例质量很差------步骤只有 1-2 步,标题就两个字"登录"。加了质量评分过滤之后,低质量用例直接被过滤掉。
坑 3:API 超时没处理。有一次 DashScope 服务抖动,API 调用卡了 2 分钟没返回,前端一直转圈。加了 30 秒超时 + 降级方案之后,超时直接返回 Mock,用户感知不到。
八、规则引擎 + AI:怎么结合才不"水"
光知道分工不够,关键是怎么把两者串起来。我们的架构是这样的:
markdown
需求文档
│
├──▶ 规则引擎(10 毫秒)
│ ├── CRUD 标准场景(增删改查各 1 条)
│ ├── 权限检查(管理员 vs 普通用户)
│ ├── 边界值测试(0、负数、超长、空值)
│ └── 必填字段验证
│ 产出:15-25 条基础用例(覆盖标准场景)
│
├──▶ AI Skill(5-30 秒)
│ ├── 复杂业务逻辑组合(需要理解需求)
│ ├── 边缘场景发现(需要创意和经验)
│ ├── 安全测试场景(注入、越权、数据泄露)
│ └── 性能测试思路(并发、大数据量)
│ 产出:5-10 条深度用例(覆盖边缘场景)
│
└──▶ 去重合并 + 质量评分
├── 标题相似度 > 80% 的合并
├── 质量分 < 0.7 的过滤
└── 最终产出:18-30 条高质量用例
规则引擎怎么写才能不"水"?
核心就一条:从需求文本中提取实体和动作,生成针对性用例,而不是套万能模板。
markdown
❌ 水的写法:
"进入功能页面 → 执行正常操作流程 → 提交操作"
✅ 不水的写法(从需求中提取):
需求:"用户可以修改商品名称和价格"
提取结果:
- 实体:商品
- 字段:名称、价格
- 动作:修改
生成用例:
用例 1:修改商品名称为有效值 → 验证名称更新成功
用例 2:修改价格为 0 → 验证系统拒绝
用例 3:修改价格为负数 → 验证系统拒绝
用例 4:修改名称为空字符串 → 验证系统拒绝
用例 5:同时修改名称和价格 → 验证两者都更新成功
标准测试模式套用:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| CRUD | 增删改查全覆盖 | 所有有数据操作的模块 |
| 权限 | 不同角色的操作权限 | 有角色区分的系统 |
| 边界值 | 数字字段的边界 | 价格、数量、年龄等数值字段 |
| 等价类 | 有效/无效输入分类 | 所有输入框 |
| 异常流 | 网络异常、数据不存在 | 所有接口调用 |
把这些模式写进规则引擎,生成的用例就不再是"万能模板",而是有针对性的测试场景。
九、总结一下
AI 生成测试用例不是"丢句话让大模型写"那么简单。从 Prompt 设计、temperature 调参、质量评分、容错处理到降级方案,每个环节都影响最终质量。
我们的经验是:规则引擎出基础用例(快、便宜、结构统一)→ AI Skill 做精细打磨(具体、深入、覆盖边缘场景)→ 测试工程师最终评审(结合业务经验补充)。三层配合,效率和质量都有保障。
我的体会是:AI 不是替代测试工程师,是放大测试工程师的价值。 规则引擎解决"有没有",AI 解决"好不好",最终还得靠测试工程师的经验决定"用不用"。
你们用 AI 生成测试用例踩过什么坑?或者有什么更好的 Prompt 技巧?评论区聊聊,我整理一期读者实战经验合集。
下篇预告:第 4 篇讲 Skill引擎本身的架构------Markdown 声明 + Python 执行器,怎么做到写个 MD 文件就能扩展新功能。