python接口自动化测试框架+ClaudeCode自动生成参数化测试数据
·
第一部分:接口自动化测试框架详解
一、框架整体设计
1.1 技术栈
- Python 3.8+ - 核心语言
- pytest - 测试执行框架
- requests - HTTP 请求发送
- pymysql - MySQL 数据库操作
1.2 项目结构
auto_api_test/
├── config/ # 配置目录
│ ├── settings.py # 核心配置(数据库、环境、常量)
├── utils/ # 工具模块
│ ├── data_driver.py # 数据驱动(参数注入)
│ ├── run_case_util.py # 测试执行
│ ├── case_util.py # 用例加载
│ ├── request_util.py # HTTP 请求发送
│ ├── assert_util.py # 断言工具
│ ├── extract_util.py # 关联值提取
│ ├── replace_util.py # 参数替换
│ ├── mysql_util.py # 数据库操作
│ ├── logger_util.py # 日志工具
│ └── exceptions.py # 自定义异常
├── testcase/ # 测试用例目录
├── upload_files/ # 上传文件目录
├── log/ # 日志目录
└── report/ # 测试报告目录
1.3 核心执行流程
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 加载用例 │ ──▶ │ 参数注入 │ ──▶ │ 关联替换 │
│ (数据库) │ │ (数据驱动) │ │ (动态参数) │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 生成报告 │ ◀── │ 执行断言 │ ◀── │ 发送请求 │
│ (统计汇总) │ │ (多维验证) │ │ (requests) │
└─────────────┘ └─────────────┘ └─────────────┘
1.4 核心模块解析
1.4.1 用例加载模块 (case_util.py)
class RdTestCase:
"""测试用例读取工具"""
def load_param_cases(self, case_table, web):
"""加载参数化用例,自动展开每条参数组合"""
base_cases = self.load_run_cases(case_table, web)
param_cases = []
for case in base_cases:
param_set_id = case.get('param_set_id')
if not param_set_id:
param_cases.append(case)
else:
# 从 test_data 表获取参数组合
param_combinations = data_driver.get_data_set(param_set_id, web)
for i, params in enumerate(param_combinations):
# 为每个参数组合生成独立用例
new_case = case.copy()
new_case['id'] = f"{original_id}_{i + 1}"
new_case['title'] = f"{case_title} [参数{i + 1}]"
# 注入参数到用例各字段
new_case = data_driver.inject_params_to_case(new_case, params)
param_cases.append(new_case)
return param_cases
核心能力:
- 从数据库
case_list表加载用例 - 自动识别
param_set_id关联参数化数据 - 将单条用例展开为多条参数化用例
1.4.2 数据驱动模块 (data_driver.py)
class DataDriver:
"""数据驱动工具类 - 支持从数据库获取测试数据并注入到用例中"""
def inject_params_to_case(self, case: Dict, params: Dict) -> Dict:
"""将参数注入到用例中 - 支持 path_params、query_params、headers 和 request_body"""
# 1. 处理 URL 路径中的参数
new_case['url'] = self._replace_in_value(new_case['url'], params)
# 2. 处理路径参数 (path_params)
new_case['path_params'] = self._replace_in_value(new_case['path_params'], params)
# 3. 处理查询参数 (query_params)
new_case['query_params'] = self._replace_in_value(new_case['query_params'], params)
# 4. 处理请求头 (headers)
new_case['headers'] = self._replace_in_value(new_case['headers'], params)
# 5. 处理请求体 (request_body)
if request_type == 'json':
new_case['request_body'] = self._process_json_body(new_case['request_body'], params)
return new_case
参数注入规则:
${param_name}占位符自动替换为参数值- 支持递归替换嵌套 JSON 结构
- 支持 URL、headers、params、body 全字段覆盖
1.4.3 关联值提取模块 (extract_util.py)
class RelationExtractor:
"""关联值提取器"""
@staticmethod
def extract_values(response_data, relation_config):
"""
从前一个接口的响应中提取值供后续用例使用
配置格式:动态名=数据源。路径
示例:token=body.data.access_token;user_id=body.data.user.id
"""
relations = relation_config.split(';')
for config in relations:
dynamic_name, field_path = config.split('=')
value = self._extract_single_value(response_data, field_path)
setattr(DynamicParam, dynamic_name, value)
支持的数据源:
body- 响应体数据headers- 响应头cookies- Cookie 数据
支持的路径格式:
- 普通路径:
body.data.user.id - 数组索引:
body.data.products[0].id - 负索引:
body.data.products[-1].id(最后一个) - JSONPath:
$.body.data[0].id
1.4.4 断言模块 (assert_util.py)
class AssertUtil:
"""断言工具类"""
@classmethod
def assert_response(cls, case: Dict, response_data: Dict) -> bool:
"""执行响应断言"""
expected_config = cls._parse_expected(case)
actual = {
'code': response_data.get('code'), # HTTP 状态码
'body_code': body.get('code'), # 业务状态码
'msg': body.get('msg'),
'data': body.get('data', {})
}
# 支持单条断言和多条断言两种模式
if 'assertions' not in expected_config:
errors = cls._assert_single(expected_config, actual)
else:
errors = cls._assert_multi(expected_config, actual)
特殊标记:
@not_null@- 验证字段不为空@ignore@- 忽略该字段
1.4.5 参数替换模块 (replace_util.py)
class RelationReplacer:
"""关联值替换器 - 处理 ${} 占位符"""
# 动态值生成器
DYNAMIC_GENERATORS = {
'timestamp()': lambda: str(int(time.time())),
'datetime()': lambda: datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'date()': lambda: datetime.now().strftime('%Y-%m-%d'),
'uuid()': lambda: str(uuid.uuid4()),
'time_ms()': lambda: str(int(time.time() * 1000)),
'today()': lambda: datetime.now().strftime('%Y%m%d'),
}
占位符类型:
- 动态参数 -
${token}从DynamicParam获取 - 嵌套路径 -
${user.id}从DynamicParam.user.id获取 - 动态生成 -
${timestamp()}实时生成时间戳
二、数据库管理用例 vs YAML 文件管理用例对比
2.1 数据库管理方案(当前框架)
表结构概览:
-- case_list 表:存储测试用例
CREATE TABLE case_list (
id INT PRIMARY KEY,
web VARCHAR(20), -- 测试环境
module VARCHAR(50), -- 所属模块
title VARCHAR(200), -- 用例标题
url VARCHAR(500), -- 接口 URL
method VARCHAR(10), -- 请求方法
headers TEXT, -- JSON 格式
request_body TEXT, -- JSON 格式
request_type VARCHAR(20),
relation VARCHAR(500), -- 关联值提取配置
expected TEXT, -- JSON 格式断言配置
param_set_id VARCHAR(50), -- 关联参数数据集
isexec INT, -- 是否执行 (1/0)
position INT -- 执行顺序
);
-- test_data 表:存储参数化数据
CREATE TABLE test_data (
id INT PRIMARY KEY,
data_set_id VARCHAR(50), -- 关联用例 ID
web VARCHAR(20),
param_values JSON, -- 参数组合 {"key":"value"}
description VARCHAR(200),
is_active INT,
sort_id INT
);
2.2 YAML 管理方案(对比方案)
典型 YAML 用例格式:
# test_login.yaml
- id: 001
title: 登录成功
url: /auth/login
method: POST
headers:
Content-Type: application/json
request_body:
username: admin
password: "123456"
expected:
code: 0
msg: 成功
- id: 002
title: 登录失败 - 密码错误
url: /auth/login
method: POST
request_body:
username: admin
password: wrong
expected:
code: 1001
典型 YAML 参数化格式:
# test_login_data.yaml
test_data:
- username: admin
password: "123456"
expected_code: 0
- username: user001
password: "password123"
expected_code: 0
- username: admin
password: wrong
expected_code: 1001
2.3 详细对比分析
| 对比维度 | 数据库管理方案 | YAML 文件管理方案 |
|---|---|---|
| 用例维护 | ||
| 新增用例 | 在线编辑,即时生效 | 需要修改文件 + Git 提交 |
| 修改用例 | 直接更新数据库记录 | 需要修改文件 + 版本控制 |
| 批量操作 | SQL 批量更新 | 脚本批量处理 |
| 版本追溯 | 需额外日志表记录 | Git 天然支持 |
| 参数化管理 | ||
| 参数配置 | 独立 test_data 表,与用例分离 | 参数内嵌在用例文件或独立 YAML |
| 参数复用 | 多条用例共享同一 data_set_id | 需要引用外部文件 |
| 动态调整 | 运行时从数据库加载最新参数 | 需要重新读取文件 |
| 协作效率 | ||
| 多人协作 | 需要数据库权限管理,可能冲突 | Git 分支 + PR 流程 |
| 非技术人员 | 可通过界面工具维护用例 | 需要了解 YAML 语法 |
| 代码评审 | 需额外审核流程 | 通过 PR 自然完成 |
| 执行效率 | ||
| 加载速度 | 需要数据库查询 (可缓存优化) | 本地文件读取更快 |
| 并发安全 | 数据库连接需管理 | 文件读取无锁竞争 |
| 可维护性 | ||
| 可读性 | JSON 字段需格式化查看 | YAML 天然可读性好 |
| 调试难度 | 需查询数据库确认配置 | 文件内容直观可见 |
| 批量修改 | SQL 语句一次性完成 | 需要脚本处理多个文件 |
2.4 选择数据库方案的理由
当前框架选择 MySQL 存储的核心原因:
-
动态性需求高
- 测试用例频繁变更(敏捷开发迭代快)
- 参数化数据需要经常调整
- 数据库方案支持"修改即生效",无需重启或重新加载
-
参数化设计
case_list与test_data分离,符合数据库范式- 多条用例可共享同一参数数据集
- 参数组合可独立管理和启用/禁用
-
环境隔离
web字段区分测试环境- 不同环境用例可独立配置
- 避免多套配置文件
-
用例录入便捷
- 使用apifox后置脚本和数据库操作,手动测试时将用例直接保存至数据库用例表,无需二次编写测试用例
三、优化方向
- CI/CD 集成 - 自动化测试流水线
- 版本控制 - 用例变更追溯和恢复
- 性能优化 - 缓存机制和批量加载
- 智能断言 - Schema 验证、数据库验证等
- 测试数据工厂 - 自动化生成测试数据
- 完善框架机制- 重试、跳过、超时、清理测试数据等机制
第二部分:利用 Skill 自动生成参数化测试数据
一、为什么需要自动化生成测试数据
传统测试数据准备的痛点:
-
手动录入效率低
- 每条参数组合都需要手动填写
- 大量重复性劳动(如姓名、手机号、邮箱等)
- 容易出错(复制粘贴错误、格式错误)
-
数据真实性差
- 随意填写的测试数据不符合业务规则
- 缺少有效的数据关联(如部门 ID 对应真实的部门)
- 无法覆盖边界值场景
-
维护成本高
- 接口字段变更后,测试数据需要手动同步
- 新增字段需要逐条补充数据
- 测试人员需要理解接口文档才能准备数据
二、Skill 触发方式与使用流程
2.1 触发关键词
当用户在对话中输入关键词时,自动触发测试数据生成流程:
示例对话:
用户:准备测试数据
AI:好的,我将为当前接口的测试用例生成参数化测试数据...
2.2 完整执行流程
Step 1: 识别用户需求
│
▼
Step 2: 查找/创建接口文档缓存
│
├─ 缓存存在 → 读取 api_doc_cache/{url_hash}.json
└─ 缓存不存在 → 调用 Apifox MCP 读取接口文档 → 保存到缓存
│
▼
Step 3: 分析接口请求参数 Schema
│
├─ 字段类型分析(string/integer/boolean...)
├─ 格式约束分析(email/phone/date...)
├─ 边界值分析(maxLength/minimum/enum...)
└─ 必填项识别(required 字段)
│
▼
Step 4: 分类处理字段
│
├─ 关联数据(re 前缀)→ 保持 ${reXxx} 占位符
└─ 业务数据(其他)→ AI 生成真实数据值
│
▼
Step 5: 生成 SQL 文件
│
├─ 输出:<原文件名>_with_data.sql
└─ 包含:INSERT INTO test_data ... 语句
│
▼
Step 6: 输出数据映射表
│
└─ 终端显示生成的数据详情
三、数据处理规则详解
3.1 两类数据的识别与处理
Skill 会自动将测试数据分为两类,采用不同的处理策略:
| 类别 | 识别规则 | 处理方式 | 示例 |
|---|---|---|---|
| 关联数据 | re 前缀的字段名 |
保持占位符,从上游接口获取 | reUserId → ${reUserId} |
| 业务数据 | 其他所有占位符 | AI 生成真实数据 | username → 张三 |
关联数据的识别逻辑:
def classify_field(field_name):
"""判断字段是否为关联数据"""
if field_name.lower().startswith('re'):
return 'relation' # 关联数据,保持占位符
else:
return 'business' # 业务数据,生成真实值
典型场景:
// 接口 A:创建用户(上游)
{
"username": "admin",
"password": "123456"
}
// 响应:
{
"userId": 1001 // 需要提取的关联值
}
// 接口 B:查询用户详情(下游)
{
"userId": "${reUserId}" // 关联数据,保持占位符
"detail": "用户详情" // 业务数据,生成真实值
}
3.2 关联值的传递机制
# 上游接口提取关联值
class RelationExtractor:
@staticmethod
def extract_values(response_data, relation_config):
# relation 配置:reUserId=body.data.userId
extracted = {"reUserId": 1001}
# 存入 DynamicParam
setattr(DynamicParam, 'reUserId', 1001)
# 下游接口使用关联值
class RelationReplacer:
@staticmethod
def replace_relations(data):
# ${reUserId} 从 DynamicParam 获取
# 替换后:{"userId": 1001}
四、AI 生成规则与字段类型映射
4.1 基础类型生成规则
| 类型 | format | 生成规则 | 示例 |
|---|---|---|---|
string |
date |
日期格式 | 2026-04-13 |
string |
date-time |
日期时间 | 2026-04-13 10:30:00 |
string |
email |
邮箱格式 | test@example.com |
string |
phone/mobile |
手机号 | 13812345678 |
string |
uri/url |
网址 | https://example.com |
string |
无 | 普通字符串 | 根据字段名生成 |
integer |
int32/int64 |
整数 | 1001、2026 |
number |
float/double |
浮点数 | 99.99 |
boolean |
- | 布尔值 | true/false |
4.2 业务字段生成规则
| 字段类型 | 生成规则 | 示例 |
|---|---|---|
| 姓名 | 真实中文姓名 | 张伟、李娜、王芳 |
| 手机号 | 中国大陆 11 位 | 13812345678、15987654321 |
| 邮箱 | 常用域名 | test@qq.com、user@163.com |
| 时间 | 合理日期 | 2026-04-10、2026-05-15 |
| 介绍/备注 | 500 字内业务描述 | 根据业务场景生成 |
| URL | 有效网址 | https://example.com/page1 |
4.3 边界值处理
| 约束类型 | 处理方式 | 示例 |
|---|---|---|
maxLength |
生成不超过最大长度的字符串 | maxLength: 20 → 张三 (2 字符) |
minLength |
生成不小于最小长度的字符串 | minLength: 5 → 张_伟_001 (6 字符) |
maximum |
生成不超过最大值的数值 | maximum: 100 → 85 |
minimum |
生成不小于最小值的数值 | minimum: 0 → 25 |
required |
必填字段必须生成值 | 不生成 null/undefined |
enum |
必须是枚举值之一 | enum: [1,2,3] → 2 |
4.4 接口文档 Schema 解析示例
OpenAPI Schema 定义:
{
"type": "object",
"properties": {
"username": {
"type": "string",
"minLength": 3,
"maxLength": 20,
"description": "用户名"
},
"email": {
"type": "string",
"format": "email",
"description": "邮箱地址"
},
"age": {
"type": "integer",
"minimum": 18,
"maximum": 100
},
"phone": {
"type": "string",
"pattern": "^1[3-9]\\d{9}$"
},
"status": {
"type": "integer",
"enum": [0, 1, 2]
}
},
"required": ["username", "email"]
}
AI 生成的数据:
{
"username": "张伟_001",
"email": "zhangwei@qq.com",
"age": 28,
"phone": "13812345678",
"status": 1
}
五、接口文档缓存机制
5.1 缓存目录结构
auto_api_test/
└── api_doc_cache/
├── _sah_ic_classInfo.json # 接口缓存 1
├── _auth_login.json # 接口缓存 2
└── _hg_companyInfo_add.json # 接口缓存 3
5.2 缓存文件命名规则
缓存文件采用 <url_path_hash>.json 格式:
| 接口 URL | 缓存文件名 |
|---|---|
/sah/ic/classInfo |
_sah_ic_classInfo.json |
/auth/login |
_auth_login.json |
/hg/companyInfo/add |
_hg_companyInfo_add.json |
5.3 缓存文件内容结构
{
"url": "/sah/ic/classInfo",
"method": "POST",
"cache_time": "2026-04-13 10:30:00",
"request_schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 50,
"description": "班级名称"
},
"startDate": {
"type": "string",
"format": "date",
"description": "开班日期"
},
"teacherId": {
"type": "integer",
"description": "教师 ID"
}
},
"required": ["name", "startDate"]
},
"response_schema": {
"code": {"type": "integer"},
"msg": {"type": "string"},
"data": {"type": "object"}
}
}
5.5 Apifox MCP 集成
准备数据前的缓存检查流程:
1. 根据用例的 url 和 method 查找 api_doc_cache/ 目录
│
├─ 缓存存在
│ └─→ 直接读取缓存中的字段类型定义
│
└─ 缓存不存在
└─→ 调用 Apifox MCP 读取接口文档
└─→ 保存到缓存文件
└─→ 读取缓存中的字段类型定义
六、输出结果与数据验证
6.1 SQL 文件输出
输出格式:
-- 文件名:test_case_001_with_data.sql
-- 生成时间:2026-04-13 10:30:00
INSERT INTO test_data (data_set_id, web, param_values, description, is_active, sort_id) VALUES
('case_list_001', 'test', '{"username": "张伟", "email": "zhangwei@qq.com", "age": 28}', '测试用例 001 - 参数组合 1', 1, 1),
('case_list_001', 'test', '{"username": "李娜", "email": "lina@163.com", "age": 32}', '测试用例 001 - 参数组合 2', 1, 2),
('case_list_001', 'test', '{"username": "王芳", "email": "wangfang@sina.com", "age": 25}', '测试用例 001 - 参数组合 3', 1, 3);
七、总结
8.1 Skill 核心价值
| 传统方式 | Skill 自动化 |
|---|---|
| 手动录入数据 | AI 自动生成 |
| 数据质量依赖个人经验 | AI 基于 Schema 智能生成 |
8.2 优化方向
-
多接口联动
- 自动分析接口依赖关系
- 生成完整的测试场景数据链
-
智能场景覆盖
- 基于历史测试数据推荐参数组合
- 自动生成边界值和异常场景数据
-
AI 增强
- 根据接口描述生成更贴合业务的测试数据
- 支持自然语言指令定制数据规则
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)