第一部分:接口自动化测试框架详解

一、框架整体设计

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'),
    }

占位符类型

  1. 动态参数 - ${token}DynamicParam 获取
  2. 嵌套路径 - ${user.id}DynamicParam.user.id 获取
  3. 动态生成 - ${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 存储的核心原因

  1. 动态性需求高

    • 测试用例频繁变更(敏捷开发迭代快)
    • 参数化数据需要经常调整
    • 数据库方案支持"修改即生效",无需重启或重新加载
  2. 参数化设计

    • case_listtest_data 分离,符合数据库范式
    • 多条用例可共享同一参数数据集
    • 参数组合可独立管理和启用/禁用
  3. 环境隔离

    • web 字段区分测试环境
    • 不同环境用例可独立配置
    • 避免多套配置文件
  4. 用例录入便捷

    • 使用apifox后置脚本和数据库操作,手动测试时将用例直接保存至数据库用例表,无需二次编写测试用例

三、优化方向

  1. CI/CD 集成 - 自动化测试流水线
  2. 版本控制 - 用例变更追溯和恢复
  3. 性能优化 - 缓存机制和批量加载
  4. 智能断言 - Schema 验证、数据库验证等
  5. 测试数据工厂 - 自动化生成测试数据
  6. 完善框架机制- 重试、跳过、超时、清理测试数据等机制

第二部分:利用 Skill 自动生成参数化测试数据

一、为什么需要自动化生成测试数据

传统测试数据准备的痛点

  1. 手动录入效率低

    • 每条参数组合都需要手动填写
    • 大量重复性劳动(如姓名、手机号、邮箱等)
    • 容易出错(复制粘贴错误、格式错误)
  2. 数据真实性差

    • 随意填写的测试数据不符合业务规则
    • 缺少有效的数据关联(如部门 ID 对应真实的部门)
    • 无法覆盖边界值场景
  3. 维护成本高

    • 接口字段变更后,测试数据需要手动同步
    • 新增字段需要逐条补充数据
    • 测试人员需要理解接口文档才能准备数据

二、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 整数 10012026
number float/double 浮点数 99.99
boolean - 布尔值 true/false
4.2 业务字段生成规则
字段类型 生成规则 示例
姓名 真实中文姓名 张伟李娜王芳
手机号 中国大陆 11 位 1381234567815987654321
邮箱 常用域名 test@qq.comuser@163.com
时间 合理日期 2026-04-102026-05-15
介绍/备注 500 字内业务描述 根据业务场景生成
URL 有效网址 https://example.com/page1
4.3 边界值处理
约束类型 处理方式 示例
maxLength 生成不超过最大长度的字符串 maxLength: 20张三 (2 字符)
minLength 生成不小于最小长度的字符串 minLength: 5张_伟_001 (6 字符)
maximum 生成不超过最大值的数值 maximum: 10085
minimum 生成不小于最小值的数值 minimum: 025
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 优化方向
  1. 多接口联动

    • 自动分析接口依赖关系
    • 生成完整的测试场景数据链
  2. 智能场景覆盖

    • 基于历史测试数据推荐参数组合
    • 自动生成边界值和异常场景数据
  3. AI 增强

    • 根据接口描述生成更贴合业务的测试数据
    • 支持自然语言指令定制数据规则
Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐