概述
前两篇我们搭好了环境,也看到了AI生成Pytest代码的效果,但AI是怎么知道要测什么的?
第一篇里,我们直接把除法函数的源代码喂给AI,让它生成测试用例。这种方式叫代码驱动。AI看实现来写测试。
但在真实项目里,你很少能只靠看代码就写出完整的测试。你需要读需求文档,需要跟产品经理对齐预期,需要知道业务规则是什么。
这就需要一个前置步骤。把人类的自然语言需求,转成结构化的测试策略。
这一篇,我们就来实现这个模块。从一段需求描述开始,让AI自动生成包含测试场景、边界条件、预期结果的完整测试策略。
意图理解模块是做什么的
用一句话说,输入自然语言,输出结构化的测试策略。
具体来看,输入是一段需求描述,比如产品经理发来的文档。输出是一个JSON格式的测试策略,包含这些字段:
json
{
"feature": "功能名称",
"test_scenarios": [
{
"scenario_id": "TC-001",
"name": "场景名称",
"description": "场景描述",
"preconditions": ["前置条件1", "前置条件2"],
"steps": ["步骤1", "步骤2", "步骤3"],
"expected": "预期结果",
"priority": "high/medium/low",
"type": "positive/negative/boundary"
}
]
}
这个JSON我们会直接喂给用例生成模块,也就是第一篇里那个生成Pytest代码的脚本,所以意图理解模块的输出格式,决定了后续所有环节的质量。
注意:意图理解模块只负责策略,它不生成任何Python代码,只输出结构化的测试设计
这样做有三个好处:
- 职责分离:意图理解管测什么,用例生成管怎么测。
- 可解释性:测试策略是JSON格式,人能直接读,也能直接改。
- 可复用:同一个测试策略,可以对接不同的代码生成逻辑。
为什么需要意图理解模块
我们先回顾一下第二篇的架构图,AI Agent测试系统有四个模块:
意图理解 → 用例生成 → 执行收集 → 分析修复
意图理解是第一环,传统模式下,这个环节是纯人工的,产品经理写一份需求文档,测试开发工程师阅读文档理解业务逻辑,设计测试场景,并把场景翻译成代码。
这道工序有三个问题:
- 信息损耗:需求文档写的和工程师理解的可能有偏差,尤其当需求写得模糊的时候,不同的人理解完全不同。
- 效率瓶颈:一个中等规模的需求,从阅读到提炼出完整测试策略,至少半天,需求频繁变更的话,这个工作要反复做。
- 知识流失:测试策略只存在于代码里,代码只告诉你测什么,不告诉你为什么这么测,导致老的测试工程师走了,新来的看不懂之前为什么设计这些用例。
AI Agent在这里的价值不是取代你理解需求,是帮你加速理解。
如何实现意图理解模块
理论讲完了,我们用代码来实际的理解一下
3.1 核心实现
python
# intent_understanding.py
import json
import os
from typing import List, Dict, Optional
from pathlib import Path
from dotenv import load_dotenv
from openai import OpenAI
class IntentUnderstandingAgent:
"""
意图理解Agent。
输入需求描述,输出结构化的测试策略JSON。
"""
def __init__(self, model: str = "gpt-4o"):
load_dotenv()
self.client = OpenAI(
api_key=os.getenv("OPENAI_API_KEY"),
base_url=os.getenv("OPENAI_BASE_URL", None)
)
self.model = model
def understand(self, requirement: str) -> Dict:
"""输入需求描述,返回测试策略。"""
prompt = self._build_prompt(requirement)
try:
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": self._get_system_prompt()},
{"role": "user", "content": prompt}
],
temperature=0.2,
response_format={"type": "json_object"}
)
result = json.loads(response.choices[0].message.content)
return result
except Exception as e:
print(f"意图理解失败: {e}")
return {"error": str(e)}
def _get_system_prompt(self) -> str:
return """
你是一位资深的测试架构师,擅长从需求描述中提取测试策略。
你的任务是将用户提供的需求描述,转化为结构化的测试策略JSON。
输出格式必须严格遵循以下结构:
{
"feature": "功能名称",
"description": "功能描述",
"test_scenarios": [
{
"scenario_id": "TC-001",
"name": "场景名称",
"description": "场景描述",
"preconditions": ["前置条件1", "前置条件2"],
"steps": ["操作步骤1", "操作步骤2", "操作步骤3"],
"expected": "预期结果",
"priority": "high|medium|low",
"type": "positive|negative|boundary"
}
],
"total_scenarios": 数字,
"coverage_notes": "覆盖情况说明"
}
设计原则:
1. 每个需求至少包含正常路径和异常路径
2. 边界条件是加分项
3. 每个场景的expected必须可验证
4. 优先级:核心功能用high,异常场景用medium,边界场景用low
5. 场景之间不要重复覆盖
"""
def _build_prompt(self, requirement: str) -> str:
return f"""
请根据以下需求描述生成测试策略:
---
{requirement}
---
请输出JSON格式的测试策略。
"""
def save_strategy(strategy: Dict, filepath: str = "test_strategy.json"):
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(strategy, f, indent=2, ensure_ascii=False)
print(f"测试策略已保存: {filepath}")
if __name__ == "__main__":
requirement = """
需求:用户登录功能
用户可以使用手机号或邮箱进行登录。
登录时需要验证密码是否正确。
登录成功后的行为:
- 跳转到个人主页
- 在顶部显示用户昵称
- 在右上角显示用户头像
登录失败的场景:
- 手机号/邮箱不存在:提示"账号不存在"
- 密码错误:提示"密码错误"
- 账号被锁定:提示"账号已被锁定,请联系客服"
安全要求:
- 连续输错5次密码后,账号锁定30分钟
- 密码输入框不支持粘贴
性能要求:
- 登录接口响应时间小于500ms(P95)
"""
agent = IntentUnderstandingAgent()
strategy = agent.understand(requirement)
if "error" not in strategy:
save_strategy(strategy, "login_strategy.json")
print(f"\n测试策略摘要:")
print(f" 功能: {strategy.get('feature', '未知')}")
print(f" 场景数: {strategy.get('total_scenarios', 0)}")
for sc in strategy.get('test_scenarios', []):
print(f" - [{sc['type']}] {sc['name']} (优先级: {sc['priority']})")
else:
print(f"生成失败: {strategy['error']}")
3.2 输出示例
运行上面的脚本,生成的login_strategy.json:
json
{
"feature": "用户登录",
"description": "用户通过手机号或邮箱进行身份验证,成功进入个人主页",
"test_scenarios": [
{
"scenario_id": "TC-001",
"name": "使用手机号正确登录",
"description": "用户使用正确的手机号和密码登录",
"preconditions": ["用户已注册,手机号13800001111,密码正确"],
"steps": [
"打开登录页面",
"输入手机号 13800001111",
"输入正确密码",
"点击登录按钮"
],
"expected": "跳转到个人主页,顶部显示用户昵称,右上角显示用户头像",
"priority": "high",
"type": "positive"
},
{
"scenario_id": "TC-002",
"name": "使用邮箱正确登录",
"description": "用户使用正确的邮箱和密码登录",
"preconditions": ["用户已注册,邮箱 test@example.com,密码正确"],
"steps": [
"打开登录页面",
"输入邮箱 test@example.com",
"输入正确密码",
"点击登录按钮"
],
"expected": "跳转到个人主页",
"priority": "high",
"type": "positive"
},
{
"scenario_id": "TC-003",
"name": "登录失败-账号不存在",
"description": "使用不存在的账号登录",
"preconditions": [],
"steps": [
"打开登录页面",
"输入手机号 13900001111(未注册)",
"输入任意密码",
"点击登录按钮"
],
"expected": "提示'账号不存在',停留在登录页",
"priority": "high",
"type": "negative"
},
{
"scenario_id": "TC-004",
"name": "登录失败-密码错误",
"description": "密码输入错误",
"preconditions": ["用户已注册,手机号13800001111"],
"steps": [
"打开登录页面",
"输入手机号 13800001111",
"输入错误密码",
"点击登录按钮"
],
"expected": "提示'密码错误',停留在登录页",
"priority": "high",
"type": "negative"
},
{
"scenario_id": "TC-005",
"name": "登录失败-账号被锁定",
"description": "已锁定账号无法登录",
"preconditions": ["账号已被管理员锁定"],
"steps": [
"打开登录页面",
"输入锁定账号的手机号",
"输入正确密码",
"点击登录按钮"
],
"expected": "提示'账号已被锁定,请联系客服',停留在登录页",
"priority": "medium",
"type": "negative"
},
{
"scenario_id": "TC-006",
"name": "连续输错密码触发锁定",
"description": "连续5次密码错误后账号锁定30分钟",
"preconditions": ["用户已注册,手机号13800001111"],
"steps": [
"打开登录页面",
"输入手机号 13800001111",
"输入错误密码",
"点击登录按钮",
"重复5次"
],
"expected": "第5次错误后提示'账号已被锁定30分钟',30分钟内无法登录",
"priority": "high",
"type": "boundary"
},
{
"scenario_id": "TC-007",
"name": "密码框禁止粘贴",
"description": "密码输入框不允许粘贴操作",
"preconditions": [],
"steps": [
"打开登录页面",
"在密码框尝试粘贴"
],
"expected": "粘贴无效,密码框内容不变",
"priority": "low",
"type": "negative"
},
{
"scenario_id": "TC-008",
"name": "登录接口响应时间",
"description": "验证登录接口响应时间",
"preconditions": ["系统正常运行"],
"steps": [
"发送登录请求",
"记录响应时间"
],
"expected": "响应时间小于500ms",
"priority": "medium",
"type": "positive"
}
],
"total_scenarios": 8,
"coverage_notes": "覆盖手机号登录、邮箱登录、账号不存在、密码错误、账号锁定、连续错误锁定、禁止粘贴、响应时间,包含2个正向、4个负向、2个边界场景"
}
8个测试场景,涵盖正向、负向、边界,功能、安全、性能三个维度,如果人工来做的话,半天工作量。但是交给AI则10秒。
3.3 加上输出校验
AI的输出有时候不稳定。字段缺失、类型错误的情况偶尔会出现,生产环境里,不要直接把AI的输出喂给下游。先做校验。
用Pydantic定义一个严格的输出模型:
python
# intent_understanding_with_validation.py
from pydantic import BaseModel, Field, validator
from typing import List, Optional
import json
class TestScenario(BaseModel):
scenario_id: str = Field(..., description="场景编号")
name: str = Field(..., description="场景名称")
description: str = Field(..., description="场景描述")
preconditions: List[str] = Field(default_factory=list)
steps: List[str] = Field(..., description="操作步骤")
expected: str = Field(..., description="预期结果")
priority: str = Field(..., description="high/medium/low")
type: str = Field(..., description="positive/negative/boundary")
@validator('priority')
def validate_priority(cls, v):
allowed = ['high', 'medium', 'low']
if v.lower() not in allowed:
raise ValueError(f'priority必须是{allowed}之一')
return v.lower()
@validator('type')
def validate_type(cls, v):
allowed = ['positive', 'negative', 'boundary']
if v.lower() not in allowed:
raise ValueError(f'type必须是{allowed}之一')
return v.lower()
class TestStrategy(BaseModel):
feature: str = Field(..., description="功能名称")
description: str = Field(..., description="功能描述")
test_scenarios: List[TestScenario]
total_scenarios: int
coverage_notes: Optional[str] = None
@validator('total_scenarios')
def validate_total(cls, v, values):
if 'test_scenarios' in values:
actual = len(values['test_scenarios'])
if v != actual:
raise ValueError(f'total_scenarios应该是{actual},实际是{v}')
return v
def validate_strategy(raw: Dict) -> Optional[TestStrategy]:
try:
return TestStrategy(**raw)
except Exception as e:
print(f"校验失败: {e}")
return None
有了这个校验,下游收到的数据一定是合法的。
3.4 导出Markdown报告
JSON是给机器用的。团队里其他人要看测试策略,给一份可读性好的文档。
python
def strategy_to_markdown(strategy: Dict, output_file: str = "test_strategy.md"):
lines = []
lines.append(f"# 测试策略:{strategy.get('feature', '未命名')}\n")
lines.append(f"> {strategy.get('description', '')}\n")
lines.append(f"**场景总数**:{strategy.get('total_scenarios', 0)}\n")
type_count = {'positive': 0, 'negative': 0, 'boundary': 0}
for sc in strategy.get('test_scenarios', []):
t = sc.get('type', '')
if t in type_count:
type_count[t] += 1
lines.append("**场景分布**:")
lines.append(f"- 正向:{type_count['positive']}个")
lines.append(f"- 负向:{type_count['negative']}个")
lines.append(f"- 边界:{type_count['boundary']}个\n")
lines.append("---\n")
for sc in strategy.get('test_scenarios', []):
priority_label = {'high': '高', 'medium': '中', 'low': '低'}
type_label = {'positive': '正向', 'negative': '负向', 'boundary': '边界'}
lines.append(f"## {sc.get('scenario_id')}:{sc.get('name')}")
lines.append(f"- **类型**:{type_label.get(sc.get('type', ''), sc.get('type'))}")
lines.append(f"- **优先级**:{priority_label.get(sc.get('priority', 'low'), '低')}")
lines.append(f"- **描述**:{sc.get('description', '')}")
if sc.get('preconditions'):
lines.append("- **前置条件**:")
for p in sc.get('preconditions', []):
lines.append(f" - {p}")
if sc.get('steps'):
lines.append("- **操作步骤**:")
for idx, step in enumerate(sc.get('steps', []), 1):
lines.append(f" {idx}. {step}")
lines.append(f"- **预期结果**:{sc.get('expected', '')}\n")
lines.append("---\n")
with open(output_file, 'w', encoding='utf-8') as f:
f.write("\n".join(lines))
print(f"Markdown报告已生成: {output_file}")
现在一份需求进去,出来三样东西:
- JSON文件,给下游模块用
- Pydantic对象,在代码里用,有类型安全
- Markdown报告,给人看,发给团队review
常见问题
Q1:为什么输出JSON,不直接输出代码?
JSON和代码的可读性不一样,如果AI生成的代码有bug,你得先看着代码改,生成的策略漏了场景,你还是得在代码里改,写过代码的同学都体会过改别人代码的痛苦。
JSON不一样,你打开文件,加一个场景,填几个字段,改完再让AI重新生成代码就行。
Q2:AI生成的测试策略能直接用吗?
不能。你得看一遍。
有时候AI会漏场景,有时候对业务规则理解偏了。但你review一个JSON比从零写快很多。
核心原则就一条:AI出初稿,你做判断。
总结
这篇做了个东西:意图理解模块。输入是一段需求描述,输出是一份测试策略JSON。
包含了三层东西:
- 核心调用:把需求发给LLM,返回结构化的JSON。
- 输出校验。用Pydantic卡一道,字段不对就报错,确保下游拿到的数据是干净的。
- Markdown报告,转成文档发给团队,能看能改。
确定了一个设计:
输出JSON,不是直接出代码。因为可改、可复用、可审查。
现在第一环搭好了,之后我们就可以把意图理解和用例生成连起来,需求进去,测试代码出来。