乐于分享
好东西不私藏

AI生成测试用例:一个Prompt模板让AI从Excel模板生成自动化脚本

AI生成测试用例:一个Prompt模板让AI从Excel模板生成自动化脚本

手写测试用例是件痛苦的事。

一个中等规模的迭代,少则几十个用例,多则上百个。每个用例要覆盖正常场景、异常场景、边界值,还要考虑前后置条件、测试数据、预期结果。机械性的重复劳动占用了我将近40%的工作时间——这不是个例,根据我观察周围团队的情况,测试用例编写和维护在整个测试周期中耗时占比普遍超过三分之一。

更让人头疼的是需求变更。接口字段改了,业务逻辑调整了,已有的用例需要同步更新。手改不仅容易遗漏,还容易出错。有段时间我甚至害怕看代码仓库里的test_文件——那些用例代码已经和需求文档严重脱节,看不懂、改不动。

把AI引入测试用例生成是我在2024年底开始的尝试。最初的动机很简单:能不能把测试数据从Excel里导入,AI帮我自动生成符合规范的测试用例代码?

这个想法落地后,效果超出了我的预期。在一个包含47个接口的模块中,我从Excel准备了完整的测试数据,AI在15分钟内生成了全部测试用例代码,经人工审核后92%可以直接使用。剩余8%需要调整的部分主要是边界值处理和异常场景的断言逻辑——这些本来就是AI的弱项,人工补充也符合预期。

接下来我会详细介绍这套方案的技术细节,包括Excel模板设计、AI解析策略、Prompt工程实践、代码质量保障,以及我在实际项目中踩过的坑和解决方案。

Excel模板的深度设计

字段设计原则

很多团队尝试用Excel管理测试数据,但设计不合理导致后续处理困难。我总结了一套经过验证的字段设计规范:

字段名
必填
说明
case_id
用例唯一标识,格式:模块_编号
case_title
用例标题,简明扼要
http_method
GET/POST/PUT/DELETE
api_path
接口路径,如 /api/v1/users
case_priority
P0/P1/P2/P3,阻塞/核心/常规/次要
case_type
功能/边界/异常/性能/安全
pre_condition
前置条件描述
test_data
测试数据,JSON格式
expected_status
预期HTTP状态码
expected_response
预期响应关键字段,JSON格式
assertion_rules
自定义断言规则
business_rules
关联业务规则说明

关键设计决策说明:

测试数据用JSON格式。早期版本我用列式存储,但复杂嵌套场景下数据可读性很差。改用JSON后,虽然单格内容变长,但结构清晰、层级分明,AI解析时也不容易出错。

// test_data 示例{  "username": "test_user_001",  "password": "Test@123",  "email": "test@example.com",  "profile": {    "age": 25,    "city": "Shanghai"  }}// expected_response 示例{  "code": 0,  "message": "success",  "data.userId": "^[A-Z0-9]{8,16}{test_data.username}"}

注意expected_response支持变量引用和正则表达式。表示用正则校验格式。

数据结构与命名规范

良好的命名规范直接影响AI生成质量。我强制团队遵循以下规则:

case_id采用模块前缀

USR_001    # 用户模块-第1个用例ORD_003    # 订单模块-第3个用例PAY_007    # 支付模块-第7个用例

case_title遵循”Given-When-Then”结构

# 良好示例"Given用户已登录_When提交有效订单_Then返回创建成功""Given商品库存为0_When用户尝试下单_Then提示库存不足"# 糟糕示例"测试下单功能""订单创建""库存不足场景"

这种结构化标题帮助AI理解测试场景的上下文,生成的断言会更精准。

多Sheet协同设计

复杂业务场景下,我会用多个Sheet组织数据:

工作簿结构:├── 接口定义        # 接口元数据├── 测试用例        # 主用例数据├── 测试数据池      # 可复用的测试数据├── 断言规则库      # 预定义断言模式└── 业务规则        # 关联业务逻辑说明
data_id
data_type
data_value
description
USER_NORMAL
用户
{“username”: “user1”, “level”: 1}
普通用户
USER_VIP
用户
{“username”: “vip1”, “level”: 3}
VIP用户
USER_ADMIN
用户
{“username”: “admin”, “role”: “admin”}
管理员
ADDR_DEFAULT
地址
{“province”: “上海”, “city”: “上海”}
默认地址

数据池设计的好处是测试数据可以复用,减少重复录入。更重要的是,AI在解析时可以识别数据间的关联关系,生成更符合业务逻辑的测试序列。

AI解析的技术细节

Prompt工程实践

Prompt设计是整个方案的核心。经过多个版本的迭代,我总结出一套高效的Prompt模板:

SYSTEM_PROMPT = """你是一个专业的测试用例生成专家,擅长将Excel中的测试数据转换为可执行的自动化测试代码。## 输出要求1. 只输出Python代码,不要包含任何解释性文字2. 使用Pytest框架3. 代码必须可以直接运行4. 每个测试函数对应Excel中的一个测试用例5. 测试函数命名规范:test_{case_id}_{简短描述}## 代码规范1. 使用requests库发送HTTP请求2. 使用pytest.mark装饰器标记用例优先级3. 使用conftest.py中定义的fixture获取base_url和headers4. 断言要具体,明确assert什么字段、什么值5. 对于有依赖的用例,在函数docstring中标注依赖关系## 变量引用规则- `{expected.field}` 引用Excel中expected_response字段- 支持正则表达式断言,格式为:assert re.match(pattern, value)## 错误处理1. 网络异常:使用pytest.skip跳过用例,标注原因2. 断言失败:使用assert的具体消息说明期望值和实际值3. 依赖失败:使用pytest.mark.dependency标记依赖关系"""USER_PROMPT_TEMPLATE = """请根据以下测试数据生成Pytest测试用例代码:### Excel数据

{excel_data}

### 项目配置- 基础URL: {base_url}- 接口前缀: {api_prefix}- 认证方式: {auth_type}### 测试类命名{test_class_name}### 注意事项1. 登录接口无需生成测试用例(已在conftest.py中处理)2. 需要认证的接口使用 @pytest.mark.usefixtures("auth_token") 装饰器3. POST/PUT请求的body使用 json= 参数4. 响应断言优先验证 code/message 字段,再验证业务数据"""

实际调用时,我会动态填充模板中的变量:

def build_user_prompt(excel_data: dict, config: dict) -> str:    """构建用户Prompt"""    return USER_PROMPT_TEMPLATE.format(        excel_data=_format_excel_data(excel_data),        base_url=config.get("base_url""http://localhost:8080"),        api_prefix=config.get("api_prefix""/api/v1"),        auth_type=config.get("auth_type""Bearer Token"),        test_class_name=f"Test{excel_data['module_name'].title().replace('_''')}"    )def _format_excel_data(data: list) -> str:    """格式化Excel数据为Markdown表格"""    if not data:        return ""    headers = list(data[0].keys())    lines = ["| " + " | ".join(headers) + " |"]    lines.append("| " + " | ".join(["---"] * len(headers)) + " |")    for row in data:        values = [str(row.get(h, ""))[:100for h in headers]  # 截断过长内容        lines.append("| " + " | ".join(values) + " |")    return "\n".join(lines)

多模型对比

我用同一个测试数据集分别测试了GPT-4、Claude-3和国内几个主流模型,结果差异明显:

指标
GPT-4
Claude-3
某国内模型
代码语法正确率
98.5%
97.2%
91.3%
断言逻辑准确性
94.1%
95.8%
82.6%
正则表达式生成
96.3%
92.1%
78.9%
异常场景覆盖
88.7%
91.2%
75.4%
单用例平均Token
850
780
920
响应速度(秒)
3.2
4.1
2.8

关键发现

GPT-4在复杂嵌套结构的JSON解析上表现最好,生成的断言代码逻辑清晰、出错率低。Claude-3对业务场景的理解略胜一筹,在异常场景识别和边界值处理上更有优势。国内模型的优势是响应速度快、价格低,但复杂场景下的代码质量不够稳定。

我的选择策略:

  • 核心业务模块(支付、订单等)用GPT-4,保证质量
  • 一般模块用Claude-3,性价比高
  • 简单CRUD接口用国内模型,快速生成

解析策略与容错

Excel数据经常存在各种不规范情况,我实现了多级容错机制:

class ExcelParser:    def __init__(self, file_path: str):        self.file_path = file_path        self.warnings = []        self.errors = []    def parse_with_validation(self) -> list[dict]:        """带验证的解析"""        raw_data = self._read_excel()        # 第一级:基础格式校验        validated_data = []        for idx, row in enumerate(raw_data, start=2):  # Excel行号从2开始            try:                parsed_row = self._validate_row(row, idx)                if parsed_row:                    validated_data.append(parsed_row)            except MissingRequiredField as e:                self.warnings.append(f"行{idx}: 缺失必填字段 {e.field},已跳过")            except InvalidFieldValue as e:                self.warnings.append(f"行{idx}: 字段 {e.field} 值无效,已使用默认值")        # 第二级:数据完整性检查        if len(validated_data) == 0:            raise EmptyDataError("没有有效的测试数据")        # 第三级:跨行关联检查        validated_data = self._resolve_references(validated_data)        return validated_data    def _validate_row(self, row: dict, row_num: int) -> dict:        """验证单行数据"""        # 检查必填字段        required_fields = ['case_id''case_title''http_method''api_path']        for field in required_fields:            if not row.get(field):                raise MissingRequiredField(field)        # 规范化http_method        method = row['http_method'].upper().strip()        if method not in ['GET''POST''PUT''DELETE''PATCH']:            method = 'GET'  # 默认值            self.warnings.append(f"行{row_num}: http_method无效,已设为GET")        row['http_method'] = method        # 解析JSON字段        for json_field in ['test_data''expected_response''assertion_rules']:            if row.get(json_field) and isinstance(row[json_field], str):                try:                    row[json_field] = json.loads(row[json_field])                except json.JSONDecodeError as e:                    row[json_field] = {}  # 空字典作为默认值                    self.warnings.append(f"行{row_num}{json_field} JSON解析失败,使用空对象")        return row    def _resolve_references(self, data: list) -> list:        """解析数据引用"""        # 构建数据池索引        data_pool = {}        for row in data:            if row.get('data_id'):                data_pool[row['data_id']] = row        # 替换引用        for row in data:            self._replace_refs(row, data_pool)        return data    def _replace_refs(self, row: dict, pool: dict):        """递归替换引用"""        for field, value in row.items():            if isinstance(value, strand value.startswith('”}USR_REG_002Given已注册用户_When再次注册_Then提示用户名已存在POST/api/v1/users/registerP1异常{“username”:“existing_user”,“password”:“Test@123456”,“email”:“existing@test.com”}200{“code”:1001,“message”:“用户名已存在”}USR_REG_003Given无效邮箱_When注册_Then提示邮箱格式错误POST/api/v1/users/registerP1异常{“username”:“new_user”,“password”:“Test@123”,“email”:“invalid-email”}200{“code”:1002,“message”:“邮箱格式不正确”}PTS_EAN_001Given用户已登录_When积分获取_Then返回积分变动POST/api/v1/points/earnP0功能{“userId”:“{USR_REG_001.userId}”}200{“code”:0,“data.balance”:“^\d+", user_id), \            f"userId格式不符,期望^[A-Z0-9]{{8,16}}", str(balance)), \            f"balance应为数字,实际: {balance}"

第四步:质量验证

生成的代码需要经过质量验证:

import astimport reclass CodeQualityValidator:    """代码质量验证器"""    def __init__(self):        self.issues = []    def validate_file(self, file_path: str) -> dict:        """验证测试文件"""        with open(file_path, 'r', encoding='utf-8'as f:            code = f.read()        # 语法检查        try:            ast.parse(code)        except SyntaxError as e:            self.issues.append(f"语法错误: {e}")        # 命名规范检查        naming_pattern = r'def (test_\w+)'        matches = re.findall(naming_pattern, code)        for func_name in matches:            if not re.match(r'test_[A-Z]{2,}_\d+', func_name):                self.issues.append(f"命名不规范: {func_name}")        return {            "file": file_path,            "issues"self.issues,            "passed"len(self.issues) == 0        }

总结

1. Excel模板设计

  • 字段精简
    :只保留必要字段,避免过度设计
  • 默认值策略
    :非关键字段设置合理的默认值
  • 命名规范
    :统一前缀便于分类和AI识别
  • JSON嵌套
    :复杂数据用JSON,减少列数

2. Prompt工程

  • 结构化输出
    :明确要求输出格式,减少解析成本
  • 示例引导
    :提供2-3个典型示例帮助模型理解
  • 变量引用
    :支持${}语法实现数据关联
  • 错误处理
    :要求模型处理异常情况

3. 质量保障

  • 多级校验
    :语法→规范→业务逻辑逐层检查
  • 人机协同
    :AI生成+人工审核,各取所长
  • 版本管理
    :保留历史版本,便于回溯
  • 指标追踪
    :统计代码通过率,持续优化

4. 迭代优化

记录每次生成的质量问题,针对性优化Prompt模板。我维护了一个问题库:

问题类型
出现频率
解决方案
断言缺失
增强Prompt中断言要求
命名不规范
提供命名示例模板
异常处理缺失
添加错误处理示例
依赖关系错误
优化数据引用语法

常见问题与解决

Q1: AI生成的代码语法错误怎么办?

原因:通常是Prompt描述不清晰或测试数据格式问题。

解决

  1. 检查Excel中JSON格式是否正确
  2. 在Prompt中增加”必须通过Python语法检查”的要求
  3. 添加语法验证环节,失败则重新生成

Q2: 断言逻辑不符合业务预期?

原因:AI对业务规则理解不准确。

解决

  1. 在expected_response中明确期望值
  2. 在business_rules字段补充业务说明
  3. 提供历史正确案例让AI参考

Q3: 生成的代码风格不一致?

原因:多模型或多次生成导致风格差异。

解决

  1. 固定使用同一模型
  2. 在Prompt中明确代码风格要求
  3. 添加格式化脚本统一代码风格

Q4: 长流程用例的依赖关系混乱?

原因:缺乏统一的依赖管理机制。

解决

  1. 使用pytest.mark.dependency标记依赖
  2. 在case_title中标注依赖关系
  3. 合理拆分长流程为多个短用例

AI生成测试用例不是要取代人工,而是把人从重复劳动中解放出来,专注于更有价值的测试设计和业务理解。把40%的机械工作时间变成10%的审核时间,这个效率提升是实实在在的。

关键是要建立一套完整的流程:好的Excel模板 → 精准的Prompt → 严格的质量验证 → 持续的效果追踪。工具只是手段,流程才是核心。

如果你也在探索AI辅助测试,欢迎交流经验。