意图理解:让Agent从需求描述自动生成Pytest测试策略

概述

前两篇我们搭好了环境,也看到了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代码,只输出结构化的测试设计

这样做有三个好处:

  1. 职责分离:意图理解管测什么,用例生成管怎么测。
  2. 可解释性:测试策略是JSON格式,人能直接读,也能直接改。
  3. 可复用:同一个测试策略,可以对接不同的代码生成逻辑。

为什么需要意图理解模块

我们先回顾一下第二篇的架构图,AI Agent测试系统有四个模块:

意图理解 → 用例生成 → 执行收集 → 分析修复

意图理解是第一环,传统模式下,这个环节是纯人工的,产品经理写一份需求文档,测试开发工程师阅读文档理解业务逻辑,设计测试场景,并把场景翻译成代码。

这道工序有三个问题:

  1. 信息损耗:需求文档写的和工程师理解的可能有偏差,尤其当需求写得模糊的时候,不同的人理解完全不同。
  2. 效率瓶颈:一个中等规模的需求,从阅读到提炼出完整测试策略,至少半天,需求频繁变更的话,这个工作要反复做。
  3. 知识流失:测试策略只存在于代码里,代码只告诉你测什么,不告诉你为什么这么测,导致老的测试工程师走了,新来的看不懂之前为什么设计这些用例。

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。

包含了三层东西:

  1. 核心调用:把需求发给LLM,返回结构化的JSON。
  2. 输出校验。用Pydantic卡一道,字段不对就报错,确保下游拿到的数据是干净的。
  3. Markdown报告,转成文档发给团队,能看能改。

确定了一个设计:

输出JSON,不是直接出代码。因为可改、可复用、可审查。

现在第一环搭好了,之后我们就可以把意图理解和用例生成连起来,需求进去,测试代码出来。

相关推荐
ZJU_统一阿萨姆1 小时前
【推理优化进阶】性能实验科学:工作负载、统计显著性与尾延迟归因
开发语言·人工智能·语言模型·系统架构·vllm
weixin_509138341 小时前
【无标题】
人工智能·agi·智能体·认知动力学·智能体认知
IT_陈寒1 小时前
Java Stream并行处理让我数据库崩了两次
前端·人工智能·后端
2601_956319881 小时前
2026年下半年,量化学习要把交易认知和技术实现接起来
人工智能·python
jay神1 小时前
深度学习为什么需要反向传播
人工智能·深度学习·yolo·目标检测·cnn·毕业设计
水水不水啊1 小时前
DeepSeek自带的一些插件的功能介绍
笔记·ai·deepseek·harness·dsh
苏灿烤鱼1 小时前
AI 会自己验证漏洞,为什么仍不能当渗透测试?
agent
EW Frontier1 小时前
【雷达信号处理】5 个阵元追平 16 个阵元:稀疏阵列 STAP 的实测报告【附python+matlab代码】
python·matlab·信号处理·雷达·mimo·稀疏阵列·stap
zhuyingxiao1 小时前
ChatGPT、Claude Code、Codex 能直接控制泵阀吗?AI Agent 液路监测的正确架构
人工智能·chatgpt·架构