AI + MCP 驱动的 CRM 接口自动化测试框架设计与实践
# AI + MCP 驱动的 CRM 接口自动化测试框架设计与实践
> 把 YAML 数据驱动、多账号会话隔离、LLM 用例生成、企业微信通知熔进一个测试框架,让测试工程师专注写 YAML,不写 Python。
**项目地址**: <https://github.com/zxpFreesky/crm-api-autotest>
***
## 一、为什么又造一个轮子
市面上的接口测试框架很多 — pytest + requests、HttpRunner、Apifox 自动化、Postman Collection。但当我们真正落地到一个企业级 CRM 系统时,遇到的问题都很具体:
| 痛点 | 具体场景 |
| ------- | ------------------------------------------ |
| 测试数据写死 | 第一次跑通了,第二次因为手机号已存在、信用代码已注册而失败 |
| 接口依赖难处理 | 创建客户后要拿到 customer\_id 才能创建联系人,硬编码 ID 维护成本高 |
| 多角色权限测试 | 销售能看、助理不能看、管理员能改 — 需要同时登录多账号 |
| 失败定位靠经验 | 测试一红,开发问"为什么红",测试人员得打开日志一行行翻 |
| 用例越积越多 | 接口字段改了,几十条用例全要改 |
| 报告推送靠截图 | CI 跑完后手动截图发群里,没人主动看 HTML 报告 |
我们的目标不是替代 HttpRunner,而是**把这些"最后一公里"问题一次性解决掉**。
***
## 二、整体架构
```
┌──────────────────────────────────────────────────────────┐
│ AI 智能决策层 │
│ 用例生成 / 语义断言 / 失败诊断 / Self-Healing │
│ (DeepSeek / 通义 / 智谱 / Kimi ...) │
└──────────────────────────┬───────────────────────────────┘
│ MCP 协议
┌──────────────────────────┼───────────────────────────────┐
│ MCP 服务层 │
│ ApiPost 元数据 │ 数据库查询 │ 报告 + 企业微信通知 │
└──────────────────────────┬───────────────────────────────┘
│
┌──────────────────────────┼───────────────────────────────┐
│ pytest 执行层 │
│ YamlCaseRunner │ SessionContext │ DataFactory │
│ LoginClientFactory (多账号会话隔离) │
└──────────────────────────┬───────────────────────────────┘
│
┌──────────────────────────┼───────────────────────────────┐
│ 持续反馈层 │
│ HTML 报告 │ AI 诊断报告 │ 企业微信 (markdown/card) │
└──────────────────────────────────────────────────────────┘
```
核心设计原则:**测试人员只维护 YAML,复杂逻辑交给框架**。
### 项目目录分层
```
auto_api_rmp_new/
├── conftest.py # pytest 全局 fixture(登录工厂/会话上下文/AI/DB 统一注入)
├── pytest.ini # pytest 配置
├── run.py # 运行入口(--all/--file/--list-cases/--list-accounts)
│
├── test_cases/ # 📁 测试执行层:pytest 测试文件
│ ├── test_login.py # 登录接口测试(YAML 数据驱动)
│ ├── test_clue.py # 线索接口测试(YAML 数据驱动)
│ └── test_ai_add_clue.py # Python 编程模式示例
│
├── datas/ # 📁 测试数据层
│ └── yaml_cases/ # YAML 数据驱动用例(推荐方式)
│ ├── login_cases.yaml
│ └── clue_cases.yaml
│
├── common/ # 📁 公共能力层
│ ├── my_request.py # HTTP 请求封装(requests.Session)
│ ├── session_context.py # 会话上下文 + 响应提取器(依赖链核心)
│ ├── data_factory.py # 动态数据工厂(@xxx 标签 / ${xxx} 引用)
│ ├── yaml_runner.py # YAML 用例执行引擎(断言/提取/解析)
│ ├── get_config.py # 配置读取(多账号角色支持)
│ ├── do_excel.py # Excel 读写(兼容历史用例)
│ ├── Log_packing.py # 日志模块(按日滚动)
│ └── contants.py # 项目路径常量
│
├── ai/ # 📁 AI 能力层
│ ├── config.py # AI 配置管理(YAML,多模型切换)
│ ├── llm_client.py # 统一 LLM 客户端(8 家国产大模型)
│ ├── case_generator.py # AI 用例智能生成(YAML + Python 双模式)
│ ├── smart_assert.py # 语义断言引擎
│ ├── self_healing.py # Self-Healing 自愈引擎
│ ├── root_cause.py # 失败根因分析引擎
│ └── prompts/ # Prompt 模板
│ ├── case_gen.py # 用例生成(四维度测试设计)
│ ├── assert_gen.py # 断言生成
│ └── analysis.py # 分析类(诊断/根因/自愈)
│
├── mcp_servers/ # 📁 MCP 服务层
│ ├── apipost_server.py # ApiPost MCP(接口元数据/调试/变更检测)
│ ├── database_server.py # 数据库 MCP(SQL 查询/表结构/数据比对)
│ └── report_server.py # 报告 MCP(结果收集/历史趋势/企业微信通知)
│
├── config/ # 📁 配置层(敏感数据,不入 Git)
│ ├── test.ini # 环境 URL / 日志 / 通知 / AI 开关
│ ├── users.yaml # 多账号角色配置(销售/助理/管理员/市场)
│ ├── ai_config.yaml # AI 模型配置(多 provider)
│ └── *.example # 配置模板(不含敏感数据,入 Git)
│
├── logs/ # 📁 运行日志(自动生成,按日滚动)
└── reports/ # 📁 测试报告(自动生成 HTML + JSON)
```
**分层职责对照:**
| 目录 | 层级 | 职责 | 谁来维护 |
|------|------|------|----------|
| `test_cases/` | 测试执行层 | 调用框架、组织测试 | 测试工程师(少量) |
| `datas/yaml_cases/` | 测试数据层 | 定义用例参数和断言 | 测试工程师(主要) |
| `common/` | 公共能力层 | 通用能力封装 | 框架维护者 |
| `ai/` | AI 能力层 | LLM 相关能力 | 框架维护者 |
| `mcp_servers/` | MCP 服务层 | 对外暴露的标准化能力 | 框架维护者 |
| `config/` | 配置层 | 环境/账号/模型配置 | 运维/测试工程师 |
**核心设计原则**:**测试人员只维护 YAML,复杂逻辑交给框架**。
---
## 三、四个关键设计
### 1. 动态数据工厂:消灭写死的数据
测试数据写死是接口测试最大的坑。我们设计了 `DataFactory`,YAML 中用 `@xxx` 标签声明,运行时动态生成:
```yaml
add_clue:
data:
customer_name: "@company" # 随机企业名
business_license_code: "@license" # 随机 18 位信用代码
phone: "@phone" # 随机手机号
province: "@province" # 联动省市区
city: "@city"
town: "@town"
remark: "@remark" # 带时间戳备注
```
每次运行生成不同数据,**永远不会因为数据已存在而失败**。`@license` 不是随机字符串,而是符合 GB 32100-2015 校验位规则的真实格式信用代码。
### 2. 会话上下文:业务依赖链的解法
CRM 系统典型的依赖链:线索 → 客户 → 联系人 → 商机。下游接口需要上游返回的 ID。
我们用 `SessionContext` + `DataExtractor` 解决:
```yaml
# 第 1 步:创建线索,自动提取 clue_id 到上下文
add_clue:
method: post
data: { customer_name: "@company", ... }
extract:
clue_id: data.id # ← 从响应提取
assert:
- { path: code, operator: in, expect: [200, 0] }
# 第 2 步:用 ${clue_id} 引用,无需硬编码
query_clue_detail:
method: get
params:
clue_id: "${clue_id}" # ← 自动替换
assert:
- { path: data.id, operator: eq, expect: "${clue_id}" }
```
`${clue_id}` 在请求发出前会被替换为上下文中的真实值,跨用例、跨账号都能用。
### 3. 多账号会话隔离
权限测试需要同时持有多个登录态。我们用 `LoginClientFactory` 解决:
```yaml
# 销售账号创建数据
add_by_sale:
account: sale_account_1 # 销售登录态
data: { ... }
extract: { entity_id: data.id }
# 助理账号查询 — 应该看不到
query_by_assistant_invisible:
account: assistant_account # 助理登录态
params:
id: "${entity_id}"
assert:
- { path: code, operator: not_in, expect: [200, 0] }
```
底层每个 account key 对应一个独立的 `requests.Session` + `SessionContext`,token 自动注入、自动缓存。同账号多次调用复用 session,**100 条用例只登录一次**。
### 4. 失败诊断:从"看日志"到"看建议"
传统测试失败只告诉你"assert code in \[200, 0] failed, actual=400"。开发拿这个信息还得自己分析为什么 400。
我们的诊断引擎做了三件事:
```
失败发生
↓
规则分析(无 LLM 也能跑)
- 分类:断言失败(正向)/ 断言失败(逆向通过)/ 网络错误 / 接口变更 / 服务端错误
- 提取:期望值 vs 实际值
- 建议:基于分类给出可操作建议
↓
AI 深度分析(可选,有 LLM 时启用)
- 结合请求体 + 响应体 + 历史结果
- 给出根因猜测
↓
历史对比
- 上次通过 → 本次失败 = 回归
- 连续 N 次失败 = 慢性 Bug
```
举个真实例子:
```
[诊断] add_clue_empty_license: 分类=断言失败(逆向通过), 风险=medium
[诊断] 断言: code 期望不在列表中 [200], 实际=200
[诊断] 建议: 逆向用例期望失败但实际通过了,说明服务端未对该场景做校验,
需确认是否为预期行为
```
这条建议直接告诉开发:"**信用代码为空居然能创建成功,可能漏了校验**"。一句话胜过一屏日志。
***
## 四、四维度测试设计:把"经验"变成"清单"
新测试工程师写用例往往只写正向,老测试工程师能想到边界、异常、场景但难复制。我们把测试经验固化成 4 个维度,**每次生成都强制覆盖**:
### 维度一:字段边界值
| 字段类型 | 必测项 |
| ---- | ------------------------------- |
| 字符串 | 最小长度、最大长度、空串、超长、特殊字符(SQL/emoji) |
| 数值 | 最小值、最大值、0、负数、超上限、非数字 |
| 枚举 | 每个合法值、非法值、空值、大小写混用 |
| 手机号 | 11 位、少位、多位、全 0、境外格式 |
| 信用代码 | 18 位、少位、已存在(唯一性冲突) |
### 维度二:必填校验
**每个必填字段至少一条缺失用例**,不合并验证:
```yaml
add_clue_missing_customer_name:
data: { ... } # 不传 customer_name
add_clue_missing_phone:
data: { ... } # 不传 phone
```
为什么不合并?因为合并验证时如果失败了,**你不知道是哪个字段导致的**。
### 维度三:业务依赖链
CRUD 完整 6 步:创建 → 查询 → 编辑 → 验证修改 → 删除 → 验证已删除。每一步都依赖上一步的 ID。
### 维度四:场景测试
- 权限场景:销售 A 看不到销售 B 的数据
- 重复场景:同一信用代码二次创建应报错
- 状态场景:已删除的记录不能再删
- 列表筛选:状态 + 日期 + 关键字组合
***
## 五、AI 是怎么接入的
不夸张地说,AI 是这个框架的"放大器",但不是"主角"。我们的设计原则:
> **AI 失败时框架必须能跑,AI 成功时框架跑得更好。**
### 用例生成:四维度 prompt
```python
prompt = f"""
基于以下接口元数据,生成 YAML 测试用例:
接口: {api_metadata['name']}
路径: {api_metadata['method']} {api_metadata['path']}
字段: {api_metadata['request']['params']}
必须覆盖四个维度:
1. 正向用例 + CRUD 业务依赖链(创建→查询→编辑→列表→删除→验证删除)
2. 必填校验:每个必填字段单独生成缺失用例
3. 字段边界值:字符串长度、数值范围、枚举非法、手机号格式
4. 场景测试:权限隔离、重复创建、列表筛选组合
输出格式:YAML,每条用例包含 name/api/method/data/extract/assert
"""
```
LLM 输出 YAML 后,自动生成对应的 Python 运行器(无需 LLM),保存为两个文件:
```
datas/yaml_cases/customer_cases.yaml # LLM 生成的用例数据
test_cases/test_customer.py # 自动生成的运行器
```
### 失败诊断:规则 + AI 双层
第一层规则分析始终执行(不依赖 LLM),输出结构化分类和建议。第二层 AI 分析在 LLM 可用时启用,针对 `assertion_failure`、`interface_changed`、`server_error` 三类高风险场景调用 LLM 深度分析。
**关键:LLM 不可用时框架照常运行,只是建议稍微粗糙一点。**
### 国产大模型适配
8 家国产大模型通过 OpenAI 兼容协议统一接入,一行配置切换:
```yaml
llm:
provider: zhipu # deepseek / qwen / moonshot / doubao / hunyuan ...
model: glm-5
api_key: "sk-xxx"
```
为什么不用 LangChain?因为我们只用 chat completion 一个能力,**直接调 OpenAI SDK 更轻量**,没有额外依赖。
***
## 六、企业微信通知:从"被动看报告"到"主动推送"
CI 跑完没人主动看 HTML 报告,但企业微信消息一定会看。我们做了三种消息类型:
| 类型 | 适用场景 | 特点 |
| --------------- | ---- | --------------- |
| `markdown` | 日常使用 | 支持颜色、加粗、@userid |
| `text` | 简单通知 | 支持手机号@(仅此类型支持) |
| `template_card` | 正式报告 | 卡片样式最专业,但无法@ |
推送时机:**测试结束 + 失败诊断完成后**自动推送,包含:
- 总用例数 / 通过数 / 失败数 / 跳过数
- 通过率 / 耗时
- 失败用例明细(最多 10 条)
- AI 诊断高/中风险项
***
## 七、配置分离:避免"一改全改"
| 文件 | 职责 | 是否提交 Git |
| ---------------- | ------------------ | ------------ |
| `test.ini` | 环境 URL、日志、通知、AI 开关 | ❌ 不提交(含真实地址) |
| `users.yaml` | 测试账号密码 | ❌ 不提交(含密码) |
| `ai_config.yaml` | LLM API Key | ❌ 不提交(含 Key) |
| `*.example` | 配置模板 | ✅ 提交 |
环境切换一行搞定:
```ini
[env]
active=test # 改成 gray / prod 立即切换
```
***
## 八、踩过的坑
### 坑 1:pytest 没统计跳过的用例
`pytest-testreport` 的 template=3 模板没有"跳过"筛选按钮。我们改了插件模板源码加上 skipped 按钮,但更重要的根因是 `_load_yaml_cases()` 在加载时就过滤了 `skip: true` 的用例,**导致 pytest 根本没收集到这些用例**。修复后让 pytest 收集全部用例,运行时再 `pytest.skip()`。
### 坑 2:dict.get 的立即求值
```python
# 看起来对,实际错了
user_data = self._users_config.get("default", self.get_user())
```
`dict.get(key, default)` 的 default 是**立即求值**的。即使 `default` 键存在,`self.get_user()` 也会被调用,导致去读已被注释的配置段。改成显式判断:
```python
if "default" in self._users_config:
return self._users_config["default"]
return self.get_user()
```
### 坑 3:根因分析输出"未分类错误: self=\<object at 0x...>"
初版根因分析直接把整个 traceback 塞进错误信息,输出全是 Python 内部对象地址。重写后优先从断言语义分类(区分正向/逆向失败),提取期望值和实际值,**输出能直接给开发看的人话**。
***
## 九、效果数据
实际跑了几个 CRM 模块(客户、线索、联系人、商机):
| 指标 | 数据 |
| -------- | ------------------ |
| 单模块用例数 | 30\~50 条(含四维度) |
| 用例编写时间 | 单接口 5\~10 分钟(YAML) |
| 重复运行稳定性 | 99%+(动态数据保证) |
| 失败诊断准确率 | 约 80%(规则 + AI) |
| 企业微信通知延迟 | < 5 秒 |
***
## 十、后续规划
1. **代码 diff 风险扫描**:开发提 MR 时自动扫描代码变更,提示影响哪些接口、建议重点测什么
2. **缺陷模式积累**:把每次失败 + 修复方案存进向量库,下次同类失败时召回
3. **Hermes Agent 接入**:通过 MCP 协议接入 Hermes,实现自然语言驱动测试("跑一下客户模块的回归")
4. **飞书/钉钉通知**:当前只支持企业微信,扩展到更多平台
5. **CI/CD 集成模板**:提供 GitHub Actions / Jenkins / GitLab CI 的现成配置
***
## 十一、总结
这个框架不是"AI 替代测试工程师",而是"**AI 把测试工程师从重复劳动中解放出来**"。
测试工程师的核心能力 — 业务理解、场景设计、风险评估 — 不会被 AI 替代。但 YAML 用例的生成、失败原因的初步定位、报告的推送分发,这些重复性工作完全可以交给框架。
我们追求的是:**让测试工程师把 80% 的时间花在设计上,20% 的时间花在执行上**。而不是反过来。
***
> **项目地址**: <https://github.com/zxpFreesky/crm-api-autotest>
> **技术栈**: Python 3.8+ / pytest / requests / YAML / 智谱 GLM / 企业微信机器人
> **License**: MIT
更多推荐

所有评论(0)