# Interface **Repository Path**: FiveDeveloper/interface ## Basic Information - **Project Name**: Interface - **Description**: 接口自动化框架:Python+Pytest+Yaml-CSV+Allure+Log+Mysql+Mock - **Primary Language**: Python - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 6 - **Created**: 2026-03-28 - **Last Updated**: 2026-04-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Interface 沟通交流群聊: ![输入图片说明](README.assets/image1.png) ## 前言 该套框架在公司内部,已经写有500个接口,亲测可用。 本框架主要基于:Python+Pytest+Yaml+CSV+Redis+Allure+Logs+Mysql+Mock 实现接口自动化框架 Git地址:https://gitee.com/make_a_summer/interface.git 项目作者:唐松(挽一夏) 个人邮箱:tasng@foxmail.com (欢迎探讨学习进步) ## 实现功能 - 测试数据隔离,实现数据驱动。使用yaml文件,或者yaml-csv文件实现单接口多组数据; - 支持一键切换环境运行,只需修改config.yaml文件即可; - 接口关联,支持内存缓存,也执行使用Redis跨文件传递,实现接口间数据依赖; - 接口数据提取:支持单个值,列表值提取,自定义字段(请求参数)取值; - 接口动态断言:支持多种断言方式,支持多场景断言,可对提取值做动态断言; - 数据库断言:直接在测试用例中写入查询的sql即可断言,无需编写代码; - yaml文件中支持用例执行前,执行SQL命令; - 支持用例熔断,创建草稿--提交订单--打印面单--订单发货。若提交订单失败,后续接口跳过; - 日志模块: 打印每个接口的日志信息; - 支持Mock接口; - 支持多线程运行,可统计接口的运行时长; - 自定义拓展字段: 如用例中需要生成的随机数据,接口加密等,可直接调用; - 企业微信&邮件通知 - AI 生成用例草稿:**默认**从本地 **API 库**按描述检索接口再让大模型拼 YAML(需先 `catalog import-har` 等入库);也可用 `--free` 纯自然语言生成(`cli.py ai2yaml` 或平台「工具」页) ## 时序图 ```mermaid sequenceDiagram autonumber %% ================= 参与者全家福 (新增 RUN 和 NOTIFY) ================= participant CI as Jenkins/User participant RUN as run.py (启动入口) participant PT as Pytest (调度主进程) participant CONF as conftest.py (全局钩子) participant DATA as DataDriver (数据驱动) participant EXE as Executor (核心执行器) participant PSR as Parser (解析替换) participant MEM as 本地内存 (context_vars) participant REDIS as 全局缓存 (Redis) participant HTTP as HttpClient (网络层) participant MOCK as MockHook (挡板拦截) participant SVR as 目标服务器 (Target Server) participant EXT as Extractor (提取引擎) participant VLD as Validator (业务断言) participant ALLURE as Allure CLI (报告生成) participant NOTIFY as 企微/邮件 (消息通知) %% ================= 阶段一:run.py 启动与测试环境装配 ================= Note over CI, DATA: 🚀 阶段一:run.py 启动与数据驱动裂变 CI->>+RUN: 执行 `python run.py` (传入 AUTO_ENV 等环境变量) RUN->>RUN: 加载环境配置,清理本地旧的 allure-results 历史结果 RUN->>+PT: 调用 `pytest.main(['--alluredir=./allure-results'])` 移交控制权 PT->>CONF: 触发 pytest_sessionstart CONF->>REDIS: 建立全局连接池,清理历史脏数据 CONF->>CONF: 建立数据库连接池 PT->>DATA: 收集阶段 (pytest_generate_tests) alt 📄 纯 YAML 数据驱动 DATA->>DATA: 直接解析 YAML 内部 List 矩阵 else 📊 YAML 模板 + CSV 数据源 DATA->>DATA: 读取 CSV 外部数据注入 YAML 模板 end DATA-->>PT: 裂变生成 N 个完全独立的并发测试实例 (TestItem) PT->>CONF: 触发 pytest_runtest_setup (单用例前置) %% ================= 阶段二:单接口核心业务流水线 ================= Note over PT, VLD: 🚀 阶段二:单实例业务流水线 (核心执行层) PT->>+EXE: 下发单步测试数据 (Step Dict) Note over EXE, EXE: 🛡️ 启动 Deepcopy 深拷贝与 ExitStack 防 OOM 托管 opt 存在 setup_sql EXE->>PSR: 解析并执行前置 SQL 准备测试数据 end %% 1. 解析与替换 Note over EXE, REDIS: 1. 核心动态解析与替换 (Parser) EXE->>PSR: 移交原始载荷 (含 ${var}, {{redis_var}}) PSR->>MEM: 1. [优先] 读取 context_vars 局部变量 PSR->>REDIS: 2. [降级] 读取 Redis 全局跨链路变量 PSR->>PSR: 3. 反射执行内置函数 (如随机单号) PSR->>PSR: ⚡ 执行正则替换引擎,交付纯净物理载荷 PSR-->>EXE: 返回组装完成的 URL, Headers, Body %% 2. 网络发包与 Mock Note over EXE, SVR: 2. 网络请求分发与 Mock 拦截 EXE->>HTTP: 委派 HttpClient 发起调用 HTTP->>MOCK: 拦截点:检查是否命中 Mock 规则 alt 🛡️ 命中 Mock 规则 MOCK-->>HTTP: 阻断外发,移交伪造 FakeResponse else 🌐 未命中 Mock HTTP->>SVR: 携带真实载荷,发起底层 TCP/HTTP 请求 SVR-->>HTTP: 目标服务器返回真实 Response end HTTP-->>EXE: 统一移交携带底层物理信息的 Response 对象 EXE->>PT: 将 URL/Header/Body/耗时/原样返回 挂载到 Allure 内存快照 %% 3. 变量四大通道提取 (结合源码还原) Note over EXE, REDIS: 3. 全维变量提取引擎 (四大通道并行) par 通道 1 & 2: 单值提取 (extract & extract_local) EXE->>EXT: 执行单值正向/逆向提取 (阻断式搜索,取首个值) EXT-->>EXE: 写入 Redis全局 + MEM本地 / 仅写入 MEM本地 and 通道 3 & 4: 列表提取 (extract_list & extract_list_local) EXE->>EXT: 执行正则/JsonPath列表全量搜刮 EXT-->>EXE: 写入 Redis全局 + MEM本地 / 仅写入 MEM本地 end %% 4. 业务断言 Note over EXE, VLD: 4. 业务断言裁决 EXE->>PSR: ⚡ 翻译断言预期规则中的动态占位符 EXE->>VLD: 传入真实预期规则与实际 Response 进行比对 VLD-->>EXE: 裁决断言结果 (Pass / 抛出 AssertionError) %% 5. 后置清理 opt 存在 teardown_sql EXE->>EXE: 执行清理 SQL,还原环境数据 end Note over EXE, EXE: 🛡️ ExitStack 自动销毁触发,优雅释放 IO 资源 EXE-->>-PT: 结束本 Step 执行,上报测试状态 %% ================= 阶段三:报告收集与生成 ================= Note over PT, NOTIFY: 🚀 阶段三:测试收尾、生成报告与消息通知 PT->>CONF: 触发 pytest_runtest_teardown (用例级清理) PT->>CONF: 触发 pytest_runtest_makereport CONF->>CONF: 捕获异常,将崩溃日志/截图强行写入 allure-results PT->>CONF: 触发 pytest_sessionfinish (释放所有的 DB/Redis 连接池) PT-->>-RUN: pytest 执行完毕,进程退出,将状态码交还给 run.py RUN->>RUN: 检查 Pytest 退出状态码 RUN->>+ALLURE: 执行 `os.system('allure generate allure-results -o allure-report --clean')` ALLURE->>ALLURE: 渲染 HTML 静态网页数据 ALLURE-->>-RUN: 报告生成完毕 RUN->>NOTIFY: 调用 Webhook,推送测试通过率与 Allure 报告在线链接 RUN-->>-CI: run.py 完美结束,退出代码 0 或 1 ``` ## 运行接口框架前准备 ```tex 1. 安装Python环境 2. 安装依赖库,pip install -r requirements.txt 3. prod.yaml文件中配置redis、mysql(本地开启Redis服务) 4. 配置config_manager.py文件,配置测试环境。 ``` Json/Yaml转换网站:[http://www.esjson.com/jsontoyaml.html](http://www.esjson.com/jsontoyaml.html) ## 目录架构 ```tex AutoFactor/ ├── business/ # 业务扩展层:存放与特定业务相关的自定义逻辑 │ ├── debugtalk.py # 供 YAML 用例热调用的动态函数库 (如生成业务签名、时间戳等) │ └── test_data_helpers.py # 测试数据合成:合法/非法样例、边界长度、脱敏(由 debugtalk 暴露为 ${...}) ├── common/ # 底层基建层:跨项目通用的技术组件,无业务状态 │ ├── helpers/ # 辅助工具箱 │ │ └── id_generator.py # 用例序列号生成器 │ ├── notify/ # 消息通知中心 │ │ ├── email_sender.py # 邮件通知发送逻辑 │ │ ├── termianl_reporter.py # 控制台打印样式 │ │ └── wechat_sender.py # 企业微信机器人推送逻辑 │ ├── readers/ # 文件解析器 │ │ ├── csv_reader.py # 纯 CSV 数据加载工具 │ │ └── yaml_reader.py # 纯 YAML 文件加载工具 │ ├── exceptions.py # 全局自定义异常类 │ └── logger.py # 全局唯一日志对象,定义日志格式与输出 ├── configs/ # ⚙️ 全局配置层:环境隔离与配置访问中心 │ ├── envs/ # 环境数据文件夹 (存放 prod.yaml, alpha1.yaml 等) │ ├── config_manager.py # 唯一配置入口:提供对象化属性调用 (如 settings.mysql.host) │ └── env_reader.py # 底层工具:负责解析 YAML 配置文件 ├── data/ # 存放CSV格式文件 ├── files/ # 存放需要上传的文件 ├── logs/ # 📝 日志输出层:存放运行时的 .log 文件 ├── testcase/ # 🧪 用例数据层:存放具体的 YAML 用例与驱动脚本 ├── reports/ # 📊 报告输出层:存放生成的 Allure 原始数据与 HTML 报告 ├── util_tools/ # 核心执行引擎层:框架运行的灵魂 │ ├── core/ # 大脑:核心调度与解析逻辑 │ │ ├── cache_manager.py # 运行时变量池:管理多进程下的变量隔离与 Redis 存取 │ │ ├── data_driver.py # 数据驱动器:处理 CSV 参数化并裂变生成用例 │ │ ├── executor.py # 核心执行器:统筹生命周期、发送请求与结果流转 │ │ ├── extractor.py # 变量提取器:从响应中提取字段并存入变量池 │ │ ├── parser.py # 模板解析器:将 YAML 中的 ${var}、${get_extract_data(id)}替换为真实值 │ │ └── validator.py # 断言校验器:执行多规则的结果验证 │ ├── clients/ # 触手:负责所有对外通信的连接器 │ │ ├── http_client.py # HTTP 客户端:封装 Session 与请求重试 │ │ ├── mysql_client.py # 数据库客户端:连接池管理与 SQL 执行 │ │ └── redis_client.py # Redis 客户端:底层的缓存读写组件 │ ├── generators/ # │ │ ├── har_yaml_converter.py# 录制转换引擎:解析HAR抓包流量,智能降噪并生成标准 YAML 用例 │ │ ├── openapi_yaml_generator.py / curl_yaml_generator.py / ai_yaml_generator.py # 文档与 AI 生成 │ └── hooks/ # 插件层:拦截并增强框架功能 │ ├── allure_hook.py # Allure 插件:动态生成测试步骤与附件 │ ├── mock_hook.py # Mock 插件:请求拦截与响应伪造 │ ├── sql_hook.py # SQL 插件:自动处理前后置数据库清理 │ └── stats_hook.py # 统计插件:收集运行数据并触发通知推送 ├── cli.py # 框架全局脚手架:record / openapi2yaml / curl2yaml / ai2csv / digest / rca / prd2json / catalog / ai2yaml ├── pytest.ini # Pytest 核心配置文件 ├── requirements.txt # 项目 Python 依赖清单 └── run.py # ▶️ 框架启动总入口:集成环境初始化与测试启动 ``` ## 接口文档 > 微信公众号-标签管理: > > https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html > > ![img_1.png](README.assets/img_1.png) | **微信公众号-标签管理** | | ----------------------- | | 登录 | | 查询标签 | | 修改标签 | | 删除标签 | | 文件上传 | 偶然看见一位B站博主的视频,干货挺多的,推荐没有接触过Pytest框架的朋友先看一遍。本框架与该博主不一致,无参考效果。 > B站博主学习资料 > > [学习推荐](https://www.bilibili.com/video/BV1Um411y7mW/?share_source=copy_web&vd_source=548f70c25aefed2ddecfdc2796827683) ## 测试用例 ### 创建测试用例 在**testcase**目录下,分别创建两个文件,yaml文件存储测试数据,py文件运行。 比如==yaml_redis_业务流程==测试文件 - 创建YAML测试文件,编写接口数据 ```yaml - name: yaml_redis:创建标签 base_url: ${get_base_url(base_wx_url)} request: method: post path: /cgi-bin/tags/create?access_token=${get_extract_data(access_token)} json: tag: name: Tag${get_timestamp(100000, 999999)} extract: redis_tag_id: $.tag.id redis_tag_name: $.tag.name validation: - code: 200 - contain: ${get_extract_data(redis_tag_id)} ``` - 创建执行YAML的Py文件,进行读取YAML文件,发送接口请求 ```python @allure.feature(next(m_id) + "yaml+redis演示方法") class TestYamlRedis: @allure.story(next(c_id) + '微信标签创建业务') @allure.description('微信标签创建业务') @pytest.mark.parametrize('caseinfo', yaml_reader('testcase/yaml_redis_业务流程/yaml_reids.yaml')) def test_yaml_reids(self, caseinfo): allure.dynamic.title(caseinfo['name']) Executor().run_case(caseinfo) ``` ### YAML用例格式规范 YAML测试文件,编写接口数据,其中有严格的关键字层级,编写时需遵守 ```yaml - name: # 用例名称 base_url: # 用例基础URL request: headers: # 请求头 (可选) cookies: # cookies (可选) method: # 请求方式 path: # 接口请求路径 data/json/params: # 请求参数 (可选) extract: # 提取参数到redis (可选) # extract_list: # 提取参数列表到redis # extract_local: # 提取参数到缓存 validation: # 断言 ``` **必填项:** ​ 4个一级关键字:name、base_url、request、validation ​ 2个二级关键字:method、path ### 接口契约断言(OpenAPI Contract) 框架新增 `validation -> contract` 断言:执行时会按当前请求的 `method + path` 到 OpenAPI 文档中定位 operation, 读取对应响应 `schema`,并对实际 JSON 响应做结构校验(基于 JSON Schema)。 ```yaml - name: contract_demo base_url: ${get_base_url(base_api_url)} request: method: GET path: /api/users/123 validation: - code: 200 - contract: openapi_file: docs/openapi.yaml # 可选:不写时默认使用当前请求 method/path、当前响应状态码 # method: GET # path: /api/users/{id} # status: "200" ``` 说明: - `openapi_file` 支持相对 `interface` 根目录的 `.yaml/.yml/.json`。 - `path` 支持参数路由匹配(如请求 `/users/123` 可匹配文档 `/users/{id}`)。 - 若文档响应未定义 `content/schema`,会按断言失败处理并给出提示。 ### Kafka 消息(可选) 依赖:`kafka-python`(已在 `requirements.txt`)。封装见 `util_tools/mq/kafka_helper.py`:`KafkaTestHelper.send`、`poll_one_after`(先 seek 到分区末尾再发再收,减少读到历史消息)。 环境变量:`KAFKA_BOOTSTRAP_SERVERS`(必填,如 `127.0.0.1:9092`);`KAFKA_DEMO_TOPIC`(可选,默认 `pytest_demo_topic`,需提前在集群中创建)。 示例用例:`testcase/test_mq_kafka_demo.py`(标记 `@pytest.mark.kafka`,未配置 broker 会自动跳过)。 ```bash set KAFKA_BOOTSTRAP_SERVERS=127.0.0.1:9092 pytest testcase/test_mq_kafka_demo.py -m kafka -n 0 ``` ### 压测 Locust(可选) 与 **功能自动化(Pytest)** 分开跑:Locust 独立进程,不承担断言业务,只做 **RPS / 延迟** 统计。 - 依赖:`pip install -r requirements-locust.txt`(不写入主 `requirements.txt`,避免所有人强装) - 脚本:`loadtest/locustfile.py`(默认压 `GET /api/health`,适合本仓库 `platform` 后端联调) - 环境变量:`LOCUST_BASE_URL`(默认 `http://127.0.0.1:8088`);`LOCUST_GET_PATH`(可选,额外压一条 GET,如 `/docs`) ```bash pip install -r requirements-locust.txt set LOCUST_BASE_URL=http://127.0.0.1:8088 locust -f loadtest/locustfile.py ``` 无界面快速跑示例:`locust -f loadtest/locustfile.py --headless -u 20 -r 5 -t 60s` ### YAML用例数据编写 一个YAML文件管理,能管理一组或者多组数据。YAML文件中有几组数据,就发送几次请求。 ​ yaml文件中有5组测试数据,所以能执行5次请求 ~~~YAML - name: yaml_redis:创建标签 base_url: ${get_base_url(base_wx_url)} request: method: post path: /cgi-bin/tags/create?access_token=${get_extract_data(access_token)} json: tag: name: Tag${get_timestamp(100000, 999999)} extract: redis_tag_id: $.tag.id redis_tag_name: $.tag.name validation: - code: 200 - contain: ${get_extract_data(redis_tag_id)} - name: yaml_redis:查询标签 base_url: ${get_base_url(base_wx_url)} request: method: post path: /cgi-bin/tags/get?access_token params: access_token: ${get_extract_data(access_token)} extract: # 单步重试配置 retry: times: 5 # 最多重试 5 次 interval: 3 # 每次间隔 3 秒 validation: - code: 200 - contain: name - name: yaml_redis:编辑标签 base_url: ${get_base_url(base_wx_url)} request: method: post path: /cgi-bin/tags/update?access_token= ${get_extract_data(access_token)} json: tag: id: ${get_extract_data(redis_tag_id)} name: test${get_timestamp(100000, 999999)} extract: validation: - code: 200 - contain: ok - name: yaml_redis:删除标签 base_url: ${get_base_url(base_wx_url)} request: method: post path: /cgi-bin/tags/delete?access_token=${get_extract_data(access_token)} json: tag: id: ${get_extract_data(redis_tag_id)} extract: validation: - code: 200 - contain: ok - name: yaml_redis:文件上传 base_url: ${get_base_url(base_wx_url)} request: method: post path: /cgi-bin/media/uploadimg?access_token=${get_extract_data(access_token)} files: files: 'files/mag.jpg' extract: validation: - code: 200 - contain: url ~~~ ![image-20260308234315676](./README.assets/image-20260308234315676.png) ### YAML+CSV管理用例 看上方发现,在YAML文件中管理多组测试数据,一组两组还好,多了之后YAML中数据就过多,不好查看与管理。 所有就引入了CSV管理,对可变的参数进行处理 - 1、CSV文件存储在data目录下 - 2、使用CSV方式做参数化,在YAML文件中,要做对应修改,新增关键字`parameters`,与对应的读取数据格式$csv{XXX} 下面使用同一个接口,展示CSV管理用例 1、data目录下,新建CSV文件,有四个字段需要被替换使用 ~~~CSV name,method,validation 正常创建_创建标签1,post,${get_extract_data(003_tag_id)} 正常创建_创建标签2,post,${get_extract_data(003_tag_name)} 创建异常_请求方式错误,get,44002 ~~~ 2、YAML测试文件,加入 **parameters** 字段,并将csv文件路径传入。被替换的字段用$csv{XXX}格式 ```yaml - name: $csv{name} base_url: ${get_base_url(base_wx_url)} # 使用csv格式,就要加入'parameters'字段。需要替换的数据使用$csv{}进行替换。 # name-method-validation 要一一对应csv文件的表头 parameters: name-method-validation: data/wx_tag_create.csv request: method: $csv{method} path: /cgi-bin/tags/create?access_token=${get_extract_data(access_token)} json: tag: name: Tag${get_timestamp(100000, 999999)} extract: 003_tag_id: $.tag.id 003_tag_name: $.tag.name validation: - contain: $csv{validation} ``` ![image-20260308234738356](./README.assets/image-20260308234738356.png) ### 使用Har文件生成Yaml用例 解决的痛点:业务流程比较长,多个接口需要每次都要抓取接口参数,从JSON格式转换Yaml格式的请求参数。下面是具体的演示 该接口未开放。请各位根据自己的项目实际替换。 第一步,在浏览器页面操作,并筛选接口后,下载har文件,可放置data目录下 ![image-20260312002132980](./README.assets/image-20260312002132980.png) ![image-20260312002327995](./README.assets/image-20260312002327995.png) 第二步:在cli.py文件中修改Yaml文件生成路径后。直接在控制台用命令生成Yaml用例 python cli.py record data/流量回放.har ~~~Python record_parser.add_argument( "-o", "--output", type=str, default="testcase/auto_gen", # --->Yaml文件生成路径 help="生成的 YAML 用例存放目录 (默认: testcase/auto_gen)" ) ~~~ 或者在控制台中,使用命令控制 ~~~shell python cli.py record data/流量回放.har # 就是默认生成Yaml文件至testcase/auto_gen python cli.py record data/流量回放.har -o testcase/har_case # 生成Yaml文件到指定目录testcase/har_case ~~~ ![image-20260312003019654](./README.assets/image-20260312003019654.png) 第三步:改改Yaml文件。把数据参数动态替换。然后复制生成一个py文件,正常走测试流程。 ![image-20260312004627887](./README.assets/image-20260312004627887.png) ### 使用 OpenAPI 3.x 生成 Yaml 骨架(无 HAR 时) 适用场景:仅有接口文档(Swagger UI 导出的 OpenAPI JSON/YAML),需要快速得到可跑的 **method / path / 示例 body** 骨架;**extract、业务 validation 需在 `testcase/draft` 中手工补全** 后再迁入正式用例目录。 ```shell # 默认输出到 testcase/draft/<文件名>_openapi_draft.yaml python cli.py openapi2yaml data/your_api.json python cli.py openapi2yaml specs/api.yaml -o testcase/draft -b "${get_base_url(base_wx_url)}" # 只导出某 tag、或限制 HTTP 方法 python cli.py openapi2yaml data/your_api.json --tag Pet --methods get,post ``` 与 `record`(HAR)并列,均由 `util_tools/generators/` 下引擎实现:HAR 为 `har_yaml_converter.py`,OpenAPI 为 `openapi_yaml_generator.py`。 ### 使用 cURL 生成 Yaml 骨架(参考 case_auto_hub 的 CurlConverter) 适用场景:从浏览器「Copy as cURL」或接口文档复制整条命令,快速得到 **method / path / headers / body**;逻辑参考 `case_auto_hub/utils/curlTrans.py`,并增强 Header 含冒号、行续接 `\\` 等场景。输出默认在 `testcase/draft/`,需自行补 **extract、validation**。 ```shell # 推荐:把整段 curl 保存为 my.curl 再转换(避免命令行转义问题) python cli.py curl2yaml data/my.curl python cli.py curl2yaml data/my.curl -o testcase/draft -n login_curl_draft.yaml -b "${get_base_url(base_wx_url)}" ``` 生成器源码:`util_tools/generators/curl_yaml_generator.py`,CLI 子命令:`curl2yaml`。 ### 使用 AI 生成 Yaml 草稿(推荐:API 库 + 搜库,对齐 aitestmind 思路) **默认行为**:根据你的自然语言描述,在本地 **API 目录库**(`data/api_catalog.db`)中做关键词检索,将匹配到的接口列表交给大模型,只基于这些接口拼出本框架 JSON,再转为 YAML。**不是**每次去解析原始 HAR 包;HAR 需先入库。 1. **采集入库**(**HAR / OpenAPI / cURL**;Playwright、mitm 可先 **导出 HAR** 再入库): ```shell python cli.py catalog import-har data/your.har python cli.py catalog import-openapi specs/api.json --tag Pet python cli.py catalog import-curl path/to/x.curl python cli.py catalog list -q login python cli.py catalog count ``` 2. **AI 搜库生成**(默认;库为空时会提示先 `import-har`): ```shell python cli.py ai2yaml "覆盖登录与订单查询的正向流程" --case-type flow -o testcase/draft ``` 3. **纯自然语言、不检索库**(旧模式,等同无 API 资产时由模型自拟 path): ```shell python cli.py ai2yaml "测试 POST /api/v1/login" --free ``` 4. **关闭 Function Calling**(仅用一次「检索 + 提示词」,不调工具链): ```shell python cli.py ai2yaml "登录流程" --no-fc ``` 5. **先 PRD 提炼测试点,再搜库生成**(`--prd` 读 Markdown;可与手写需求合并;`--prd-no-ai` 仅用启发式抽测试点,不调模型): ```shell python cli.py ai2yaml --prd docs/feature_prd.md python cli.py ai2yaml --prd docs/feature_prd.md "优先覆盖登录与订单查询" python cli.py ai2yaml --prd docs/feature_prd.md --prd-no-ai ``` **前置**:`OPENAI_API_KEY` / `DEEPSEEK_API_KEY` 等;可选 `OPENAI_BASE_URL`、`AI_MODEL`、`configs/envs/*.yaml` 中 `ai` 段。 生成器:`util_tools/generators/ai_yaml_generator.py`;目录库:`util_tools/catalog/`。平台:`POST /api/catalog/import-har`、`POST /api/catalog/import-openapi`、`POST /api/tools/ai-to-yaml`(`from_catalog` 默认 true,`use_function_calling` 默认 true)。 **与 aitestmind 能力对齐(子集)**:采集入本地库(HAR / OpenAPI / cURL)→ `hierarchical_search_apis` / `get_api_detail` / `submit_test_plan` 的 **Function Calling** 链路(见 `util_tools/generators/ai_catalog_fc.py`);Playwright、mitm 录制可 **先导出 HAR** 再 `catalog import-har`;**可视化 Flow 编排**未内置,可继续用 YAML 或外挂 aitestmind 后导出对接。 ### PRD / 用户故事 Markdown → 测试设计 JSON(D1/D2,人工审) 从需求文档抽取**用户故事、流程、规则、风险、测试范围与优先级矩阵**,落盘为 JSON,**不直接覆盖生产用例**;可与后续 `ai2yaml` / 手写 YAML 对照使用。 - **实现**:`util_tools/generators/prd_analyze.py`;CLI:`prd2json`。 - **无 Key / 离线**:`--no-ai` 仅按 `#` 标题与 `-`/编号列表做启发式结构化(可复现)。 - **有 Key**:与 `ai2yaml` 相同环境变量 / `configs/envs/*.yaml` 的 `ai` 段,语义填充固定 schema;未配置 Key 时自动降级为启发式并打标 `meta.note`。 ```shell python cli.py prd2json docs/your_prd.md python cli.py prd2json docs/your_prd.md --no-ai python cli.py prd2json docs/your_prd.md -o testcase/draft/prd/custom.json ``` 默认输出:`testcase/draft/prd/<文件名>_prd_analysis.json`。 ### AI 生成测试数据 CSV(数据驱动) 调用大模型按场景生成 **合成** 测试数据表(UTF-8 CSV),供 `parameters` + `$csv{列名}` 与 `data_driver` 使用(与 `data/wx_tag_create.csv` 一类用法相同)。**不训练模型**,使用与 `ai2yaml` 相同的 OpenAI 兼容接口与 Key 配置。 ```shell python cli.py ai2csv "电商下单:覆盖正常下单、库存不足、未登录、优惠券不可用" -n 15 -o data/order_ai_cases.csv python cli.py ai2csv -f docs/scenario.txt --columns case_name,user_id,sku,qty,expect --rows 20 -o data/cart_matrix.csv python cli.py ai2csv "生成支付回调测试行" --context-file docs/openapi_snippet.txt -n 8 -o data/pay_cb.csv ``` - `--columns`:强制表头(模型必须对齐);省略则由模型自行设计列名。 - `--context-file`:附加字段说明、枚举或 OpenAPI 片段。 - `--no-strict-rows`:允许模型返回行数与 `-n` 不完全一致仍写入(默认严格一致)。 实现:`util_tools/generators/ai_testdata_csv.py`;平台:`POST /api/tools/ai-to-csv`。 ### 失败归因 / 执行后简报(规则优先,LLM 可选) 流水线:**原始日志 → 规则脱敏**(手机、邮箱、身份证、Token 等)→ **规则层摘要**(截断 + ERROR/断言等关键字行)→ **可选** `--ai` 调用与 `ai2yaml` 相同的大模型配置,生成「现象 / 可能类别 / 建议下一步」简报(**仅供参考,不定责**)。 ```shell python cli.py digest logs/2026-01-01/app.log type err.txt | python cli.py digest --stdin -o reports/failure_digest.md python cli.py digest logs/app.log --ai -o reports/digest_ai.md python cli.py digest logs/app.log --rules-only ``` - 默认 **不调模型**,仅脱敏 + 规则摘要;加 `--ai` 才把**已脱敏**内容送模型。 - CI:可在失败步骤后增加 `if errorlevel 1 python cli.py digest ...`。 - 实现:`util_tools/diagnostics/failure_digest.py`。 #### 与「完整 AI 根因分析平台」的边界(必读) 网上/厂商描述的「分钟级、90%+ 准确率、自动关联 Git/监控/全链路」需要 **多源数据采集与治理**(代码仓、CI diff、APM、日志平台、调用链 ID、历史缺陷库等),**不可能**仅靠本框架目录里的 pytest 日志就等价实现。 本仓库提供的是 **轻量草稿层**: | 能力 | `digest` | `rca`(结构化 JSON) | |------|----------|----------------------| | 脱敏 + 规则摘要 | ✅ | ✅ | | LLM 归纳 / 五维分类草稿 | 可选 `--ai` | 默认调模型(可用 `--no-ai` 仅规则) | | 输出 | 可读 Markdown 风格文本 | **JSON**(`primary_category`、`hypotheses`、`suggested_actions`、`limitations`) | | Git / 监控 / 链路 / 历史库 | ❌ 需外挂 | ❌ 同上 | **结构化根因草稿(五维归因)示例:** ```shell python cli.py rca logs/app.log -o reports/rca_draft.json python cli.py rca logs/app.log --no-ai -o reports/rca_rules.json type err.txt | python cli.py rca --stdin -o reports/rca.json ``` `rca` 在提示词中要求模型填写 `limitations`,**禁止**在缺少证据时宣称已定位根因;人工仍须结合 Allure、接口与环境核对。 ## 用例中提取参数 #### 提取方式介绍 根据接口实际结果使用。均支持使用**jsonpath**,**正则表达式** ##### 提取参数至Redis中 ​ extract :提取单个数据 ​ extract_list : 提取的数据是列表 ##### 提取参数至内存缓存中: ​ extract_local: 提取单个数据 ​ extract_list_local: 提取的数据是列表 ##### 内存缓存与Redis适用场景 内存缓存适用场景: ​ 一个yaml文件中,写了完整的接口业务流程,那么使用内存缓存是最合适的,接口提取参数,只在yaml文件内接口中使用,用完即销毁。 Redis缓存适用场景: ​ 一个yaml文件中写一个接口,py文件中存在不同yaml接口用例,接口直接关联参数,需要跨文件传参,那么使用redis来存储是最合理的。 ### 提取单个数据-Redis #### 提取接口返回值,指定字段。 ##### 使用jsonpath提取 extract字段中,根据接口返回值,写入jsonpath表达式 ~~~yaml - name: yaml_redis:创建标签 base_url: ${get_base_url(base_wx_url)} request: method: post path: /cgi-bin/tags/create?access_token=${get_extract_data(access_token)} json: tag: name: Tag${get_timestamp(100000, 999999)} extract: redis_tag_id: $.tag.id redis_tag_name: $.tag.name validation: - code: 200 - contain: ${get_extract_data(redis_tag_id)} ~~~ 接口返回值: ~~~json {"tag":{"id":466,"name":"Tag820515"}} ~~~ 接口运行成功后,提取存入至Redis中 ![image-20260309000959035](./README.assets/image-20260309000959035.png) ##### 使用正则表达式提取 extract字段中,根据接口返回值,写入正则表达式 ~~~yaml - name: yaml_redis:创建标签 base_url: ${get_base_url(base_wx_url)} request: method: post path: /cgi-bin/tags/create?access_token=${get_extract_data(access_token)} json: tag: name: Tag${get_timestamp(100000, 999999)} extract: # 正则提取数字 id redis_tag_id: '"id":\s*(\d+)' # 正则提取字符串 name redis_tag_name: '"name":\s*"(.*?)"' validation: - code: 200 - contain: ${get_extract_data(redis_tag_id)} ~~~ 运行成功后,成功写入Redis中 ##### 提取指定参数 当接口返回多组数据,只要其中符合条件的字段 yaml文件中,创建标签接口---查询接口。创建标签后,根据标签名称提取标签的id ~~~yaml - name: yaml_redis:创建标签 base_url: ${get_base_url(base_wx_url)} request: method: post path: /cgi-bin/tags/create?access_token=${get_extract_data(access_token)} json: tag: name: Tag${get_timestamp(100000, 999999)} extract: redis_tag_id: $.tag.id redis_tag_name: $.tag.name validation: - code: 200 - contain: ${get_extract_data(redis_tag_id)} - name: yaml_redis:查询标签 base_url: ${get_base_url(base_wx_url)} request: method: post path: /cgi-bin/tags/get?access_token params: access_token: ${get_extract_data(access_token)} extract: caseid: "$.tags[?(@.name=='${get_extract_data(redis_tag_name)}')].id" validation: - code: 200 - contain: name ~~~ ~~~json {"tags":[{"id":2,"name":"星标组","count":0},{"id":411,"name":"Tag128","count":0},{"id":412,"name":"Tag726399","count":0},{"id":413,"name":"Tag997593","count":0},{"id":416,"name":"Tag187","count":0},{"id":417,"name":"Tag423","count":0},{"id":418,"name":"Tag931269","count":0},{"id":419,"name":"Tag978659","count":0},{"id":421,"name":"Tag672160","count":0},{"id":422,"name":"Tag397593","count":0},{"id":424,"name":"Tag425","count":0},{"id":429,"name":"Tag163733","count":0},{"id":430,"name":"Tag789642","count":0},{"id":431,"name":"test769","count":0},{"id":432,"name":"test857819","count":0},{"id":433,"name":"Tag161445","count":0},{"id":434,"name":"Tag934713","count":0},{"id":435,"name":"Tag320499","count":0},{"id":436,"name":"Tag702782","count":0},{"id":437,"name":"Tag359850","count":0},{"id":438,"name":"Tag496765","count":0},{"id":439,"name":"test310135","count":0},{"id":440,"name":"test811199","count":0},{"id":441,"name":"Tag480","count":0},{"id":442,"name":"test648","count":0},{"id":443,"name":"Tag576","count":0},{"id":444,"name":"Tag462827","count":0},{"id":445,"name":"Tag575675","count":0},{"id":446,"name":"test745221","count":0},{"id":447,"name":"test750","count":0},{"id":448,"name":"Tag626","count":0},{"id":449,"name":"Tag465","count":0},{"id":450,"name":"Tag387561","count":0},{"id":451,"name":"Tag801753","count":0},{"id":452,"name":"test968295","count":0},{"id":453,"name":"test414142","count":0},{"id":454,"name":"test359","count":0},{"id":455,"name":"test229789","count":0},{"id":456,"name":"Tag587032","count":0},{"id":457,"name":"Tag599066","count":0},{"id":458,"name":"test570217","count":0},{"id":459,"name":"Tag498482","count":0},{"id":460,"name":"Tag221366","count":0},{"id":461,"name":"Tag771105","count":0},{"id":462,"name":"Tag241545","count":0},{"id":463,"name":"Tag434436","count":0},{"id":464,"name":"Tag792478","count":0},{"id":465,"name":"Tag359792","count":0},{"id":466,"name":"Tag820515","count":0},{"id":467,"name":"Tag565251","count":0},{"id":468,"name":"Tag704110","count":0}]} ~~~ 指定提取name = Tag704110下的id,存入Redis中 ![image-20260309001927955](./README.assets/image-20260309001927955.png) #### 提取单个数据-内存缓存 一个yaml文件中编写用例,使用内存缓存存储接口关联数据 ~~~yaml - name: yaml_内存关联:创建微信标签 base_url: ${get_base_url(base_wx_url)} # 定义在该流程内通用的初始变量,供下方所有步骤调用 variables: {} # 业务步骤列表 (steps),框架会按照列表顺序,从上到下逐一执行 steps: - name: yaml_cache:创建标签 request: method: post path: /cgi-bin/tags/create?access_token=${get_extract_data(access_token)} json: tag: name: Tag${get_timestamp(100000, 999999)} # 提取单个值 extract_local: csv_tag_id: $.tag.id csv_tag_name: $.tag.name validation: - code: 200 - contain: ${csv_tag_id} - name: yaml_cache:查询标签 request: method: post path: /cgi-bin/tags/get?access_token params: access_token: ${get_extract_data(access_token)} # 提取列表 extract_list_local: all_ids: "$.tags[*].id" validation: - code: 200 - contain: name ~~~ ![image-20260309002806829](./README.assets/image-20260309002806829.png) ### 提取列表-Redis 查询标签接口,返回值中所有的**id**。分别使用正则与jsonpath提取,使用**extract_list**关键字 接口返回 ```json { "tags": [ { "id": 2, "name": "星标组", "count": 0 }, { "id": 100, "name": "22214767", "count": 0 }, { "id": 129, "name": "case", "count": 0 }, { "id": 130, "name": "case60", "count": 0 }, { "id": 131, "name": "case86", "count": 0 }, { "id": 153, "name": "哈哈哈63", "count": 0 }, { "id": 154, "name": "哈哈哈45", "count": 0 }, { "id": 155, "name": "哈哈哈54", "count": 0 } ] } ``` 正则提取,提取列表使用extract_list关键字,提取所有id值 ```yaml - name: wx-查询标签 base_url: ${get_base_url(base_wx_url)} request: method: post path: /cgi-bin/tags/get?access_token params: access_token: ${get_extract_data(access_token)} extract_list: all_ids: '"id":\s*(\d+)' validation: - code: 200 - contain: name ``` jsonpath提取,提取列表使用extract_list关键字,提取所有goodsId值 ```yaml - name: wx-查询标签 base_url: ${get_base_url(base_wx_url)} request: method: post path: /cgi-bin/tags/get?access_token params: access_token: ${get_extract_data(access_token)} extract_list: all_ids: "$.tags[*].id" validation: - code: 200 - contain: name ``` 运行成功,存入Redis中 ![img_4.png](README.assets/img_4.png) ### 接口参数使用 #### 单个参数使用 这一步就是接口关联的实际用法。上一个接口返回值中提取后存入,下一个接口中读取使用 固定格式写法:${get_extract_data(xxx)} 比如查询标签---编辑标签接口 查询标签,提取name = case86的id,编辑标签中使用该id ~~~yaml - name: wx-编辑标签 base_url: ${get_base_url(base_wx_url)} request: method: post path: /cgi-bin/tags/update?access_token= ${get_extract_data(access_token)} json: tag: id: ${get_extract_data(caseid)} name: test${get_random_number(100000, 999999)} extract: validation: - code: 200 - contain: ok ~~~ #### 多个参数使用 上面提取了列表,并存储,那么就到了使用的时候 Redis中,存入多个值,yaml文件中引用。 id: ${get_extract_data(all_ids,1)},根据索引取第一个值。0随机取值,-1取全部值。方法在debugtalk.py文件中,可以自行扩展。 ```yaml - name: wx-编辑标签 base_url: ${get_base_url(base_wx_url)} request: method: post path: /cgi-bin/tags/update?access_token= ${get_extract_data(access_token)} json: tag: id: ${get_extract_data(all_ids,1)} name: test${get_random_number(100000, 999999)} extract: validation: - code: 200 - contain: ok ``` ## 用例中接口传参如何实现 1、通过上个接口发送请求,得到token请求值,存储到redis中 2、YAML文件中,通过debugtalk.py文件中,使用read_extract_redis方法,获取redis中数据 读取关联值,因为提取的有单个数据、列表数据,所以提取的方式略有不同 单个参数,如上述`token`值,在YAML文件中,使用${get_extract_data(token)} 列表参数,如上述`goodIds`第1个值,在YAML文件中,使用${get_extract_data(goodIds,0)} redis中,已经存储数据 编写方式,需要替换取extract.yaml文件值的地方,使用$ {get_extract_data(xxx)},列表使用${get_extract_data(xxx,位数)} ```yaml # 微信删除标签接口,如需传入token params: token: ${get_extract_data(token)} # 编辑标签中,如想编辑第1个标签,就取第一个id值:2 json: id: ${get_extract_data(all_ids,1)} ``` ## 单用例重试 解决问题:因为网络抖动导致的其中一个接口运行失败。或者当前接口因为需要获取某些数据获取到,才能进行下一步。 比如打印面单,得到物流商返回的运单号后,才能打印面单,那么在yaml中增加关键字**retry** ~~~yaml - name: yaml_redis:查询标签 base_url: ${get_base_url(base_wx_url)} request: method: post path: /cgi-bin/tags/get?access_token params: access_token: ${get_extract_data(access_token)} extract: caseid: "$.tags[?(@.name=='${get_extract_data(redis_tag_name)}')].id" # 新增单步重试配置 retry: times: 5 # 最多重试 5 次 interval: 3 # 每次间隔 3 秒 validation: - code: 200 - contain: name ~~~ ## 接口执行前执行SQL 在接口执行之前,需要造数据、清理数据 ~~~yaml - name: yaml_redis:查询标签 base_url: ${get_base_url(base_wx_url)} # 接口运行之前,执行SQL setup_sql: - "UPDATE t_sku SET `name` = 'test022520' WHERE id = 2101100417" request: method: post path: /cgi-bin/tags/get?access_token params: access_token: ${get_extract_data(access_token)} extract: caseid: "$.tags[?(@.name=='${get_extract_data(redis_tag_name)}')].id" # 新增单步重试配置 retry: times: 5 # 最多重试 5 次 interval: 3 # 每次间隔 3 秒 validation: - code: 200 - contain: name # 接口运行之后,执行SQL teardown_sql: - "DELETE FROM t_temu_y2_order_17 WHERE erp_no = 'ZY4H20250731001'" ~~~ ## 用例中函数扩展使用 在使用的过程中,发现需要扩展一些方法函数,可以在debugtalk.py文中编写实现。比如看例子时候发现,URL、headers、cookies都是${xx()} 格式。这就是在debugtalk.py文件中定义了写函数,并进行替换使用。如 `${get_base_url()}、${get_headers()}、 ${get_extract_data(Cookie)}` 这些方法均在debugtalk.py文件中编写,yaml用例中使用。 ```python class DebugTalk: # 基础url @classmethod def get_base_url(cls, node_name): """ :param node_name: base_url :return: """ return read_config_file('base', node_name) # mysql数据库 @classmethod def get_mysql_config(cls, node_name): """ :param node_name: mysql :param node_name: :return: """ return read_config_file('mysql', node_name) # headers请求头 @classmethod def get_headers(cls, params_type): headers_mapping = { 'data': {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'}, 'json': {'Content-Type': 'application/json;charset=UTF-8 '} } header = headers_mapping.get(params_type) if header is None: raise ValueError('不支持其他类型的请求头设置') return header # 随机数 @classmethod def get_random_number(cls, min, max): return random.randint(int(min), int(max)) ``` 微信创建标签接口时,就可以使用随机数函数 ```yaml - name: wx-创建标签 base_url: ${get_base_url(base_wx_url)} request: method: post path: /cgi-bin/tags/create?access_token=${get_extract_data(access_token)} json: tag: name: test${get_random_number(10000, 99999)} extract: validation: - code: 200 ``` ## 断言类型 本框架只实现了五种类型断言 `状态码断言、包含模式断言、相等断言、不相等断言、数据库断言` ```yaml validation: - code: 200 # 接口的响应状态码断言 - contain: access_token # 包含模式 - eq: { 'expires_in': 7200 } # 相等断言 - ne: { 'expires_in': 72001111 } # 不相等断言 - db: # 数据库断言 sql: "SELECT erp_no FROM t_b2b_outbound WHERE puid=145 AND outbound_no='${get_extract_data(b2b_outboundNo)}'" # SQL语句 eq: {erp_no: '${get_extract_data(b2b_outbound_erpNo)}'} # 相等断言,期望值=SQL语句 - db: sql: "SELECT erp_no,outbound_no FROM t_b2b_outbound WHERE puid=145 AND outbound_no='${get_extract_data(b2b_outboundNo)}'" # SQL语句 contain: { outbound_no: '${get_extract_data(b2b_outboundNo)}' } # 包含断言,期望值在查询的SQL语句内 ``` ## 熔断机制 5个接口,其中第3个接口运行失败,后续接口会直接跳过 ![image-20260309003621161](./README.assets/image-20260309003621161.png) ## allure报告 执行run.py文件,在reports文件allures文件下,打开index.html文件,即可查看 收到邮箱通知 ![img.png](README.assets/img2.png)