# Interface **Repository Path**: make_a_summer/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**: 50 - **Forks**: 6 - **Created**: 2024-12-15 - **Last Updated**: 2026-04-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: Python, pytest, Yaml ## README # Interface 自动化测试框架 ## 一、前言 欢迎来到 Interface 自动化测试框架! 本框架是一套面向接口自动化测试的工程化解决方案,核心栈为 **Python + Pytest + YAML + CSV + Redis + Mock + Allure + MySQL**。 它的设计目标是让测试工程师**不写代码或只写极少量代码**,就能完成复杂接口链路的自动化测试。 读完这份手册,你将能够: 1. 独立配置运行环境并启动测试 2. 编写标准的 YAML 接口用例 3. 掌握变量提取、数据驱动、数据库断言、多步骤链路等高阶能力 4. 看懂框架的运行日志和 Allure 报告 该套框架在公司内部,已经写有500个接口,亲测可用 - 项目作者:唐松(挽一夏) - 个人邮箱:tasng@foxmail.com (欢迎探讨学习进步) Json/Yaml转换网站:[http://www.esjson.com/jsontoyaml.html](https://gitee.com/link?target=http%3A%2F%2Fwww.esjson.com%2Fjsontoyaml.html) B站学习视频推荐:[【软件测试接口自动化】 ](https://www.bilibili.com/video/BV1Um411y7mW/?share_source=copy_web&vd_source=548f70c25aefed2ddecfdc2796827683) PS:本框架与推荐视频不一致,没有关联关系。推荐新同学先看一遍,方便了解。 接口文档: > 微信公众号-标签管理: > > [https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html](https://gitee.com/link?target=https%3A%2F%2Fdevelopers.weixin.qq.com%2Fdoc%2Foffiaccount%2FBasic_Information%2FGet_access_token.html) ![img_1.png](README.assets/img_1.png) 沟通交流群: ![7bb861d1911ce6161c46150b94b223dc](./README.assets/7bb861d1911ce6161c46150b94b223dc.jpg) --- ### 流程图 ~~~mermaid graph TD %% 样式定义 (去除了可能引发解析错误的尾部标点) classDef trigger fill:#e3f2fd,stroke:#1e88e5,stroke-width:2px classDef engine fill:#fff3e0,stroke:#fb8c00,stroke-width:2px classDef core fill:#e8f5e9,stroke:#43a047,stroke-width:2px classDef tool fill:#f3e5f5,stroke:#8e24aa,stroke-width:1px classDef infra fill:#eceff1,stroke:#546e7a,stroke-width:1px classDef target fill:#ffebee,stroke:#e53935,stroke-width:2px,stroke-dasharray: 5 5 %% 1. 触发与入口层 subgraph Layer1 [1. 触发与入口层] Git[Git 代码合并]:::trigger --> Jenkins[Jenkins CI/CD]:::trigger Jenkins --> RunPY[run.py 全局启动脚本]:::trigger LocalCLI[本地终端 CLI]:::trigger --> RunPY end %% 2. 数据驱动与调度层 subgraph Layer2 [2. 数据驱动与调度层] RunPY ==> Pytest[Pytest 核心调度引擎]:::engine Pytest --- Conftest[conftest.py 全局生命周期]:::engine Pytest --- DataDriver[DataDriver 多模态数据驱动]:::engine Pytest --- ConfigMgr[ConfigManager 多环境装配]:::engine end %% 3. 核心执行引擎层 subgraph Layer3 [3. 核心执行引擎层] Pytest ==> Executor[Executor 场景控制总线]:::core Executor --- MemPool[(context_vars 本地内存池)]:::core Executor --- ExitStack{ExitStack IO防泄露托管}:::core end %% 4. 基础业务工具链 subgraph Layer4 [4. 基础业务工具链] Executor ==> Parser[Parser 动态占位符解析器]:::tool Executor ==> Http[HttpClient 发包与流量路由]:::tool Executor ==> Extractor[Extractor 四大通道提取]:::tool Executor ==> Validator[Validator 智能预期断言]:::tool end %% 5. 底层基建与目标服务 subgraph Layer5 [5. 底层基建与目标服务] Conftest -.->|1. 初始化跨链路缓存| Redis[(Redis 全局共享缓存池)]:::infra Conftest -.->|2. 初始化数据隔离| MySQL[(MySQL 数据库)]:::infra Parser -.->|降级读取| Redis Extractor -.->|全局写入| Redis Executor -.->|前后置钩子执行| MySQL Http -.->|命中规则流量劫持| MockHook{MockHook 挡板拦截器}:::infra Http ==> TargetServer((目标被测服务器)):::target MockHook -.->|阻断物理外发| TargetServer end %% 6. 报告与通知层 subgraph Layer6 [6. 报告与通知层] Executor ==> AllureData[Allure JSON 原始快照]:::trigger RunPY ==> AllureCLI[Allure CLI 渲染 HTML 网页]:::trigger AllureData -.->|组装源数据| AllureCLI RunPY ==> WeChat[企微/邮件 Webhook 捷报推送]:::trigger end ~~~ ### 时序图 ```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 ``` ### 实现功能 #### 1. 数据驱动引擎(YAML / CSV 双轨制) - **YAML 原生多组数据**:单个 YAML 文件可直接编写多组独立用例,框架通过 `yaml_reader` 自动解析为 Pytest 参数化用例。 - **YAML + CSV 笛卡尔积裂变**:通过 `parameters` 节点绑定 CSV 文件,实现单接口多组数据的自动化裂变。支持多 CSV 源的笛卡尔积组合,同时通过 `ast.literal_eval` 保证数字、布尔、字典等类型的 100% 保真。 - **参考样例**:`testcase/yaml_csv_业务流程` #### 2. 多步骤业务链路编排(steps + teardown_steps) - **steps 主链路**:支持将多个接口按顺序编排为一条完整业务链路,步骤间通过 `extract` / `extract_local` 共享变量,实现"创建 -> 查询 -> 修改 -> 删除"的闭环测试。 - **teardown_steps 环境兜底**:无论主链路成功或失败,框架都会强制执行 teardown 清理,防止脏数据污染测试环境,且 teardown 报错不会掩盖主链路的真实错误。 - **参考样例**:`testcase/yaml_内存关联_业务流程` #### 3. 双级变量池体系(Redis 全局 + 内存局部) - **Redis 全局变量池(extract / extract_list)**:支持跨 YAML 文件、跨进程的变量共享,天然兼容 `pytest-xdist` 多进程并发。底层采用 Redis Pipeline 批量写入,降低网络 RTT 开销。 - **内存局部变量池(extract_local / extract_list_local)**:变量仅存在于当前用例生命周期内,适合临时中间值,避免污染全局缓存。 - **请求体逆向拦截($.request.)**:支持从已发出的请求头、请求体、查询参数中逆向提取数据(如 TraceId、Nonce、签名参数),解决"发送了但响应没回显"的提取难题。 - **参考样例**:`testcase/yaml_redis_业务流程` #### 4. 软断言与多模态断言体系 - **软断言(Soft Assert)**:原生 `assert` 是"一断就停",本框架采用聚合报错模式,遍历完所有断言规则后一次性抛出完整错误清单,避免"修一个报一个"的低效排障。 - **断言类型全覆盖**: - `code`:HTTP 状态码严格比对 - `eq`:字典严格全等(仅校验最外层指定字段) - `ne`:反向不等(常用于验证修改生效) - `contain`:深度包含匹配,支持 JSONPath 精确路径(`$.data.status`)和全层级模糊扫描(`status`) - `db`:数据库落库校验,支持 `eq` / `contain` / `empty` 三种模式 - **SQL 安全拦截**:数据库断言内置 DML/DDL 黑名单(insert/update/delete/drop/truncate/alter/create),仅允许 `SELECT` 查询穿透,防止测试框架误改生产数据。 #### 5. 失败重试与智能熔断 - **单步轮询重试(retry)**:针对微服务异步延迟和网络抖动,单步接口支持配置 `times` + `interval` 轮询重试。网络异常和业务断言失败均会触发重试,耗尽后正式抛错。 - **用例级熔断机制(Circuit Breaker)**:基于 Redis 维护的熔断黑名单,当前置依赖用例失败时,后续关联用例自动跳过(Skip),并在 Allure 中标记"已熔断"。用例重试成功后自动"自愈"清除熔断标记。 - **瞬态故障豁免**:配合 `pytest-rerunfailures` 使用时,非末轮的失败被识别为"瞬态抖动",不会触发熔断,保障重试管线的自我恢复能力。 #### 6. Mock 接口与流量仿真 - **请求拦截网关**:在 YAML 中配置 `mock` 节点后,框架会在真实 HTTP 请求发出前进行拦截,直接返回伪造的 Response(可自定义 status_code、json、headers 等)。 - **HAR 流量录制回放**:通过 `cli.py record` 命令,可将浏览器抓包的 HAR 文件自动转换为标准 YAML 测试用例,并智能推导基础断言规则,实现"录制一次,回放 N 次"的回归测试。 #### 7. 高性能与工程化保障 - **orjson 极速解析**:响应体 JSON 解析、断言序列化、Redis JSON 存取等核心链路已全部替换为 Rust 核心的 `orjson`,在大型 WMS 仓储报文场景下速度提升 5~10 倍。 - **多进程日志隔离**:基于 PID 隔离日志文件(`logs/日期/时间_pid{pid}.log`),配合 `propagate=False` 彻底避免 `pytest-xdist` 并发下的日志交错与重复打印。 - **大响应体截断保护**:当接口返回超大 HTML 报错页或巨型 JSON 时,控制台日志自动截断(>500字符),Allure 附件仍保留完整数据,防止 Jenkins 控制台卡死。 - **连接池优化**:HTTP Session 全局单例 + Keep-Alive 长连接;MySQL 连接池支持从配置动态调整 `maxconnections`;Redis 连接池上限扩至 50,并开启 TCP KeepAlive 与 health_check。 #### 8. Allure 深度集成与可观测性 - **步骤分层可视化**:多步骤用例在 Allure 报告中以折叠面板形式清晰展示"步骤1、步骤2",teardown 步骤带独立图标区分。 - **请求溯源附件**:每个接口的请求 URL、Headers、Cookies、Payload、Response 均被组装为表格附件挂载到报告中。 - **环境信息自动注入**:测试结束后自动生成 `environment.properties`,包含 Python 版本、Git 分支、Jenkins 构建号、测试环境名等上下文。 #### 9. 动态函数扩展库(DebugTalk) - **热调用机制**:YAML 中可直接通过 `${函数名(参数)}` 调用 Python 函数,无需编写额外代码。 - **内置函数覆盖**:毫秒时间戳、随机 Nonce、UUID 单号生成、环境配置读取、Redis 变量读取与格式化、HMAC-SHA256 签名生成等。 - **自定义扩展**:在 `common/debugtalk.py` 中新增方法后,YAML 即可实时反射调用,框架会自动对幂等函数启用 LRU 缓存,对非幂等函数(如时间戳)强制走实时计算。 #### 10. 统计通知与 CI/CD 集成 - **运行统计**:`stats_hook` 自动收集用例数、成功率、失败率、平均耗时、重试补偿次数等数据。 - **多渠道通知**:测试结束后自动触发终端图表渲染,并根据配置开关选择性推送邮件 / 企业微信机器人通知。 - **Jenkins 无缝集成**:自动嗅探 `BUILD_URL`、`JOB_NAME`、`BUILD_NUMBER` 等环境变量,动态生成 Allure 在线访问链接。 ## 二、环境准备 ### 2.1 安装依赖 #### 2.1.1 按照依赖包 ```bash pip install -r requirements.txt ``` #### 2.1.2 安装环境配置 ~~~ 1. 本地电脑环境:安装Python3、JDK8、Allure包 2. 启动本地Redis、MySQL服务。并在prod.yaml文件中配置redis、mysql ~~~ 关键依赖清单: - `pytest`:测试执行引擎 - `pytest-xdist`:多进程并发执行 - `pytest-rerunfailures`:失败重试 - `allure-pytest`:测试报告生成 - `redis`:跨进程变量共享 - `pymysql` + `dbutils`:数据库连接池 - `orjson`:高性能 JSON 解析(比原生 json 快 5~10 倍) ### 2.2 配置测试环境 框架通过 `AUTO_ENV` 环境变量来决定加载哪个环境配置文件。 ```bash # Windows PowerShell $env:AUTO_ENV="test7.yaml" python run.py # Linux / Mac export AUTO_ENV=test7.yaml python run.py ``` 如果不设置,默认加载 `configs/envs/prod.yaml`。 环境配置示例 (`configs/envs/test7.yaml`): ```yaml host: "http://api.example.com" base_url: base_wms_url: "http://example.com" mysql: host: "mysql.example.com" port: 3306 username: "user" password: "123456" database: "uesr" redis: host: "redis.example.com" port: 6379 db: 0 password: "" expire: 3600 environment: clientId: "your_client_id" userId: "your_user_id" clientSecret: "your_secret" ``` ### 2.3 启动你的第一个测试 ```bash python run.py ``` 执行完毕后,Allure 报告会自动生成在 `reports/allures/index.html`。 --- ## 三、项目结构速览 ```text Interface/ ├── common/ # 底层基建层 │ ├── debugtalk.py # 动态函数库(签名、造数、时间戳) │ ├── exceptions.py # 全局自定义异常体系 │ ├── logger.py # 全局日志(多进程隔离) │ ├── notify/ # 通知中心(邮件、企业微信) │ │ ├── email_sender.py # 邮件通知发送逻辑 │ │ ├── terminal_reporter.py # 控制台样式 │ │ └── wechat_sender.py # 企业微信机器人推送逻辑 │ ├── readers/ # 文件读取器(YAML、CSV) │ │ ├── csv_reader.py # 纯 CSV 数据加载工具 │ │ └── yaml_reader.py # 纯 YAML 文件加载工具 │ └── helpers/ # 辅助工具箱 │ └── id_generator.py # 生成用例ID ├── configs/ # ⚙️ 全局配置层:环境隔离与配置访问中心 │ ├── envs/ # 环境数据文件夹 (存放 prod.yaml, alpha1.yaml 等) │ │ ├── prod.yaml # 存放prod环境配置 │ │ └── test7.yaml # 存放test7环境配置 │ ├── config_manager.py # 唯一配置入口:提供对象化属性调用 (如 settings.mysql.host) │ └── env_reader.py # 底层工具:负责解析 YAML 配置文件 ├── data/ # 存放csv/har等文件 ├── files/ # 存放上传的文件 ├── logs/ # 📝 日志输出层:存放运行时的 .log 文件 ├── reports/ # 📊 报告输出层:存放生成的 Allure 原始数据与 HTML 报告 ├── templates/ # 邮件通知样式 ├── testcase/ # 🧪 用例数据层:存放具体的 YAML 用例与驱动脚本 ├── util_tools/ # 核心执行引擎层:框架运行的灵魂 │ ├── clients/ # 触手:负责所有对外通信的连接器 │ │ ├── http_client.py # HTTP 客户端:封装 Session 与请求重试 │ │ ├── mysql_client.py # 数据库客户端:连接池管理与 SQL 执行 │ │ └── redis_client.py # Redis 客户端:底层的缓存读写组件 │ ├── core/ # 大脑:核心调度与解析逻辑 │ │ ├── cache_manager.py # 运行时变量池:管理多进程下的变量隔离与 Redis 存取 │ │ ├── data_driver.py # 数据驱动器:处理 CSV 参数化并裂变生成用例 │ │ ├── executor.py # 核心执行器:统筹生命周期、发送请求与结果流转 │ │ ├── extractor.py # 变量提取器:从响应中提取字段并存入变量池 │ │ ├── parser.py # 模板解析器:将 YAML 中的 ${var} 替换为真实值 │ │ └── validator.py # 断言校验器:执行多种规则的结果验证 │ ├── generators/ # 拓展 │ │ └── har_yaml_converter.py # hra文件替换 │ └── hooks/ # 插件层:拦截并增强框架功能 │ ├── allure_hook.py # Allure 插件:动态生成测试步骤与附件 │ ├── mock_hook.py # Mock 插件:请求拦截与响应伪造 │ ├── sql_hook.py # SQL 插件:自动处理前后置数据库清理 │ └── stats_hook.py # 统计插件:收集运行数据并触发通知推送 └── cli.py # HAR 流量录制工具 ├── conftest.py # Pytest 全局钩子(熔断、清理、统计) ├── pytest.ini # Pytest 配置(并发数、重试次数) ├── requirements.txt # 项目 Python 依赖清单 └── run.py # ▶️ 框架启动总入口:集成环境初始化与测试启动 ``` --- ## 四、5 分钟写一个接口用例 ### 4.1 写 YAML 用例文件 在 `testcase/api_auto/demo/` 下新建 `create_order.yaml`: ```yaml - name: 创建订单 base_url: ${get_base_url(base_wms_url)} request: method: post path: /api/order/create headers: Content-Type: application/json json: sku: ${get_base_data(skuA)} count: 1 validation: - code: 200 - eq: { 'code': 0 } ``` ### 4.2 写 Python 驱动文件 在同级目录下新建 `test_demo.py`: ```python import allure import pytest from common.readers.yaml_reader import yaml_reader from util_tools.core.executor import Executor @allure.feature('演示模块') class TestDemo: @allure.story('创建订单') @pytest.mark.parametrize('caseinfo', yaml_reader('testcase/api_auto/demo/create_order.yaml')) def test_create_order(self, caseinfo): allure.dynamic.title(caseinfo['name']) Executor().run_case(caseinfo) ``` ### 4.3 运行 ```bash python run.py ``` 完成!你刚刚完成了框架的**最小可用闭环**。 --- ## 五、YAML 用例语法全景手册 ### 5.1 单接口用例(最简结构) ```yaml - name: 查询库存 base_url: ${get_base_url(base_wms_url)} request: method: post path: /api/inventory/query headers: Content-Type: application/json json: sku: ${get_base_data(skuA)} warehouse: ${get_base_data(warehouseCode)} extract: available_qty: $.data.qty validation: - code: 200 - eq: { 'code': 0 } - contain: { 'status': 'ok' } ``` **字段说明**: - `name`:用例名称(会显示在 Allure 报告中) - `base_url`:基础域名,支持变量 `${get_base_url(xxx)}` - `request`:HTTP 请求核心配置 - `method`:GET / POST / PUT / DELETE / PATCH - `path`:接口路径 - `url`:完整地址(若写了 url 则忽略 base_url + path) - `headers` / `cookies` / `params` / `data` / `json` / `files`:标准 HTTP 参数 - `timeout`:超时时间(秒),默认 20 秒 - `extract`:变量提取规则 - `validation`:断言规则列表 ### 5.2 多步骤链路用例(steps + teardown_steps) 当业务流程需要多个接口串联时,使用 `steps` 定义主链路,`teardown_steps` 定义清理链路。 ```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 - name: yaml_cache:编辑标签 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: ${csv_tag_id} name: test${get_timestamp(100000, 999999)} extract_local: validation: - code: 200 - contain: ok - name: yaml_cache:删除标签 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: ${csv_tag_id} # 直接引用上一步提取的内存变量 extract_local: validation: - code: 200 - contain: ok teardown_steps: # 无论主链路成功或失败,都会执行 - name: yaml_cache:文件上传 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_local: validation: - code: 200 - contain: url ``` **关键机制**: - `steps` 里的 `extract` 提取的变量,会自动写入**当前用例的内存池**,后续步骤可直接通过 `${变量名}` 引用。 - `teardown_steps` 是"兜底清理机制":即使 `steps` 中某一步断言失败,框架也会先执行完 teardown,再抛出真实错误。 ### 5.3 数据驱动(parameters + CSV) 当同一个接口需要跑多组参数时,使用 `parameters` 绑定 CSV 文件,实现**笛卡尔积裂变**。 **CSV 文件** (`data/wx_tag_create.csv`): ```csv name,method,validation 正常创建_创建标签1,post,${get_extract_data(003_tag_id)} 正常创建_创建标签2,post,${get_extract_data(003_tag_name)} 创建异常_请求方式错误,get,44002 ``` **YAML 文件**: ```yaml - name: $csv{name} base_url: ${get_base_url(base_wx_url)} 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} ``` **重要提示**: - `parameters` 的 Key 是**CSV 列名的组合**(用 `-` 连接),如 `name-method-validation`。 - Value 是 CSV 文件路径,相对于项目根目录。 - 框架会对所有 `parameters` 源执行**笛卡尔积**:若有 2 个 CSV 源,分别有 3 行和 2 行,最终生成 `3 × 2 = 6` 条用例。 - `$csv{xxx}` 是 CSV 占位符,会被替换为对应列的值。 - CSV 单元格内容如果以 `{` 或 `[` 开头,框架会自动用 `ast.literal_eval` 推导为字典/列表,实现**类型保真**。 ### 5.4 变量提取体系(extract 的四种形态) 框架支持从**响应体**和**请求体**中提取变量,供后续步骤使用。 #### 5.4.1 extract(落盘 Redis,跨文件共享) ```yaml extract: token: $.data.token order_id: $.data.id ``` - 提取的变量会写入 **Redis**,并带上进程级前缀,天然支持 `pytest-xdist` 多进程并发。 - 跨 YAML 文件可以通过 `${get_extract_data(token)}` 读取。 #### 5.4.2 extract_local(仅内存,当前文件内共享) ```yaml extract_local: temp_qty: $.data.qty ``` - 变量**不落 Redis**,仅存在于当前用例的内存池。 - 适合存放临时中间值,避免污染全局缓存。 #### 5.4.3 extract_list(列表提取,落盘 Redis) ```yaml extract_list: sku_id_list: $.data.items[*].id ``` - 提取一整列数据,结果为 Python 列表,写入 Redis。 - 后续可用 `${get_extract_data(sku_id_list)}` 读取,或用 `${get_extract_data(sku_id_list, 0)}` 取第一个元素。 #### 5.4.4 extract_from_request(从请求体逆向拦截) 某些业务参数(如签名 TraceId、防重放 Nonce)只在请求头里发送,服务端响应中不会回显。如果需要在后续断言中校验这些参数,就需要从**已发出的请求**里提取。 ```yaml extract: trace_id: $.request.headers.Trace-Id req_erpNo: $.request.json.erpNo ``` - 表达式必须以 `$.request.` 开头。 - 支持 `headers`、`json`、`params` 三个段落的提取。 - 支持多级嵌套,如 `$.request.json.addressVo.zipCode`。 ### 5.5 断言体系(validation) `validation` 是一个列表,框架会**遍历执行所有断言**,最后一次性汇总报错。这被称为"软断言"。 #### 5.5.1 状态码断言(code) ```yaml validation: - code: 200 ``` #### 5.5.2 严格相等(eq) 检查字典中指定的 Key 是否存在于实际结果的**最外层**,且值严格相等。 ```yaml validation: - eq: { 'code': 0, 'msg': 'success' } ``` #### 5.5.3 包含判断(contain) 支持三种形态: **基础类型包含**: ```yaml validation: - contain: "success" ``` **字典深度包含**:自动启用 JSONPath 雷达,在全层级扫描 Key。 ```yaml validation: - contain: status: "ok" user.name: "admin" ``` **精确 JSONPath 包含**:Key 以 `$.` 开头,直接定位。 ```yaml validation: - contain: "$.data.list[0].status": 1 ``` #### 5.5.4 反向不等(ne) 常用于验证"修改后值已变化"。 ```yaml validation: - ne: { 'code': 1000 } ``` #### 5.5.5 数据库断言(db) 框架会直接执行 SQL,并将查询结果与预期值比对。 ```yaml validation: - db: sql: "SELECT outbound_no FROM t_b2b_outbound WHERE id = 1" eq: { outbound_no: 'OB123456' } - db: sql: "SELECT count(*) as cnt FROM t_order WHERE status = 1" contain: { cnt: 5 } - db: sql: "SELECT id FROM t_deleted_order WHERE order_no = 'D001'" empty: True ``` **安全机制**:框架内置了 SQL 关键字黑名单拦截。任何包含 `insert`、`update`、`delete`、`drop`、`truncate`、`alter`、`create` 的 SQL 都会被直接熔断,仅允许 `SELECT` 查询穿透。 ### 5.6 重试机制(retry) 针对微服务架构下的异步延迟和网络抖动,单步接口支持配置轮询重试。 ```yaml - name: 查询订单状态 request: method: post path: /api/order/status json: orderNo: ${order_no} retry: times: 5 # 最多重试 5 次(含首次共 5 次) interval: 3 # 每次间隔 3 秒 validation: - eq: { 'data.status': 'shipped' } ``` **触发条件**: - 网络异常(Timeout、ConnectionError) - 业务断言失败(ValidationException、AssertionError) 重试次数耗尽后,框架会正式抛错并将用例标红。 ### 5.7 Mock 机制 当下游接口不可用或需要模拟异常场景时,可在用例中配置 `mock`。 ```yaml - name: 模拟下游超时 mock: status_code: 504 json: code: -1 msg: "Gateway Timeout" request: method: post path: /api/order/create validation: - code: 504 ``` `mock` 配置后,框架会**拦截真实 HTTP 请求**,直接返回伪造的 Response。 --- ## 六、变量解析引擎(Parser) 框架内置了强大的变量替换引擎,支持 `${变量}` 和 `${函数()}` 两种语法。 ### 6.1 纯变量替换(类型保真) ```yaml json: orderId: ${order_id} # 如果 order_id 在内存池中是 int,替换后依然是 int ``` ### 6.2 字符串混编(强制降级为字符串) ```yaml path: /api/order/${order_id}/detail ``` ### 6.3 动态函数调用 ```yaml json: erpNo: '${generate_order_number(erp_)}' timestamp: ${get_timestamp()} nonce: ${get_nonce()} sign: ${hmac_sha256_sign(/api/order/create)} ``` ### 6.4 跨文件读取 Redis 变量(get_extract_data) 当变量通过 `extract` / `extract_list` 写入 Redis 后,跨 YAML 文件或跨进程读取时必须使用 `${get_extract_data(变量名)}`。 **基础用法**: ```yaml json: orderNo: ${get_extract_data(order_no)} token: ${get_extract_data(accessToken)} ``` **多态格式化(out_format)**: `get_extract_data` 的第二个参数是格式化指令,专门用于对 Redis 中存储的列表型数据进行清洗和降维: | out_format | 作用 | 示例说明 | | ----------- | -------- | ----------------------------------------------------- | | 不传 / None | 原始返回 | 列表则随机取一个元素,字典/字符串/数字直接返回 | | `0` | 随机抽样 | 从列表中均匀随机抽取一个元素 | | `-1` | 序列降维 | 将列表拼接为逗号分隔的字符串,常用于 SQL 的 `IN` 语法 | | `-2` | 类型纯化 | 剔除列表中的非数字项,全部转为 `int` 列表 | | 正整数 `N` | 绝对索引 | 取列表的第 `N` 个元素(注意是 1-based 索引) | **实战示例**: ```yaml # 假设 Redis 中存了 sku_id_list = [101, 102, 103] json: # 取第一个 SKU skuId: ${get_extract_data(sku_id_list, 1)} # 随机取一个 SKU randomSku: ${get_extract_data(sku_id_list, 0)} # 拼成 SQL 的 IN 参数字符串:"101,102,103" skuInParam: ${get_extract_data(sku_id_list, -1)} # 全部转为 int 列表(过滤掉脏数据) skuIdList: ${get_extract_data(sku_id_list, -2)} ``` **注意**:`get_extract_data` 只能读取通过 `extract` / `extract_list` 落盘到 Redis 的变量。如果是 `extract_local` / `extract_local_list` 写入的内存变量,在当前 YAML 文件内直接通过 `${变量名}` 引用即可,无需也不能用 `get_extract_data` 读取。 常用动态函数清单(定义在 `common/debugtalk.py`): 根据项目实际情况,进行配置。脱敏删除部分代码。 | 函数 | 说明 | | ------------------------------- | --------------------------------------- | | `get_timestamp()` | 毫秒级时间戳 | | `get_nonce()` | 2 位随机数 | | `generate_order_number(prefix)` | 生成带前缀的随机单号 | | `get_base_url(key)` | 从环境配置读取基础域名 | | `get_env_value(key)` | 从 environment 节点读取配置 | | `get_base_data(key)` | 从 base_web_data 节点读取枚举值 | | `get_extract_data(key, format)` | 从 Redis 读取提取的变量,支持多态格式化 | | `hmac_sha256_sign(path)` | 生成 HMAC-SHA256 签名 | --- ## 七、全局运行机制(conftest.py) 框架在 `conftest.py` 中挂载了多个 Pytest 生命周期钩子,新人无需修改,但建议了解其运作原理: ### 7.1 pytest_sessionstart(会话启动) - 仅 Master 进程执行 - 清理过期日志目录 - 清空 Redis 测试数据 - 预加载熔断器状态到本地 L1 缓存 ### 7.2 pytest_collection_finish(用例收集完成) - 批量检查所有用例文件的熔断状态 - 若某文件的前置依赖用例曾失败,则将该文件标记为"已熔断" ### 7.3 pytest_runtest_call(用例执行前) - 查询本地缓存,判断当前用例是否处于熔断状态 - 若是,则跳过执行(Skip),并在 Allure 报告中注入"已熔断"标识 - **容错放行**:如果当前用例恰好就是触发熔断的故障源本身(如 rerun 场景),则予以放行 ### 7.4 pytest_runtest_makereport(报告生成) - 用例失败后,自动向 Redis 写入熔断标记 - 用例成功后,自动清除该文件的熔断标记(自愈机制) - 智能识别重试:非末轮的失败被视为"瞬态抖动",不触发熔断 ### 7.5 pytest_terminal_summary(测试结束) - 汇总统计信息 - 生成 `environment.properties` 供 Allure 展示 - 触发邮件/企业微信通知 --- ## 八、性能优化与工程实践 ### 8.1 并发执行(pytest-xdist) 在 `pytest.ini` 中配置: ```ini [pytest] addopts = -vs --alluredir=reports/temps --clean-alluredir --reruns 2 --reruns-delay 1 -n 3 --dist=loadfile ``` - `-n 3`:启动 3 个 worker 进程并发执行 - `--dist=loadfile`:同一个文件内的用例在同一个进程执行,确保 Redis/内存变量隔离正确 ### 8.2 日志隔离 在 `pytest-xdist` 模式下,每个 worker 进程拥有独立的 PID,日志文件按 PID 隔离存储在 `logs/日期/` 目录下,彻底避免并发写冲突。 ### 8.3 大响应体保护 当接口返回超大 HTML 报错页或巨型 JSON 时,框架会自动截断控制台日志(>500 字符截断),同时 Allure 附件保留完整数据,防止 Jenkins 控制台卡死。 ### 8.4 orjson 极速解析 框架核心链路(响应解析、断言序列化、Redis JSON 存储)已全部替换为 `orjson`,在大型 WMS 仓储报文场景下解析速度提升 5~10 倍。 --- ## 九、新人 FAQ ### Q1:为什么我的 `${变量}` 没有被替换? 检查以下几点: 1. 变量名是否拼写正确,区分大小写 2. 变量是否在 `extract` / `extract_local` 中正确提取 3. 如果是跨文件变量,检查是否使用了 `extract`(而非 `extract_local`)写入 Redis 4. 变量是否含有未闭合的引号或特殊字符,导致 YAML 解析异常 ### Q2:Allure 报告中用例名显示为 `$csv{name}`,没有替换? CSV 数据驱动的名称替换发生在 `pytest.mark.parametrize` 之后。建议在 `test_xxx.py` 的驱动方法中不要硬编码 Allure 标题,而是依赖 `allure.dynamic.title(caseinfo['name'])`,这样 Pytest 会正确显示裂变后的用例名。 ### Q3:数据库断言报"SQL 安全拦截"? 框架为了防范误操作,仅允许 `SELECT` 语句。如果你的 SQL 里包含了 `insert`、`update`、`delete`、`drop`、`truncate`、`alter`、`create` 等关键字,会被直接熔断。测试数据预埋请使用 `setup_sql`,测试清理请使用 `teardown_sql` 或 `teardown_steps`。 ### Q4:用例失败后为什么后续用例都被跳过了? 这是框架的**熔断机制**在生效。默认情况下,若某个用例文件失败,框架会将该文件标记为"已熔断",避免无效执行。你可以在 `pytest.ini` 中调整重试次数,或在本地调试时单文件运行来规避。 ### Q5:如何只运行某一个 YAML 文件? ```bash pytest testcase/api_auto/demo/test_demo.py -vs ``` ### Q6:如何调试单个步骤? 打开 `logs/日期/` 目录下对应 PID 的日志文件,搜索用例名称。框架会完整打印:请求 URL、请求头、请求体、实际响应体、断言结果。 --- ## 十、推荐阅读路径 如果你是第一次接触本框架,建议按以下顺序学习: 1. **跑通一个单接口用例**:复制 `testcase/测试文件样式/01_单条yaml文件_变量从Redis读取` 2. **学习多步骤链路**:阅读 `testcase/测试文件样式/02_单条yaml文件_runner类型_变量从内存读取` 3. **尝试数据驱动**:阅读 `testcase/测试文件样式/03_单条yaml文件_csv驱动` 4. **理解跨文件组合**:阅读 `testcase/测试文件样式/04_多条yaml文件` 5. **深入核心源码**:阅读 `util_tools/core/executor.py`、`validator.py`、`extractor.py`,每个方法都有详尽的注释和示例 祝你在 Interface 框架里写用例愉快! ## 熔断机制 5个接口,其中第3个接口运行失败,后续接口会直接跳过 ![image-20260309003621161](./README.assets/image-20260309003621161.png) ## allure报告 执行run.py文件,在reports文件allures文件下,打开index.html文件,即可查看 收到邮箱通知 ![img.png](README.assets/img2.png) ## Jenkins Pycharm提交代码,自动触发运行 & 定时触发运行,可以直接配置。如果需要再发布环境自动运行,需要找运维配置。 ![image-20260404195201643](./README.assets/image-20260404195201643.png) ### 指定环境运行 General ☑️ 参数化构建过程。选项就是代码中configs/envs目录下的yaml环境文件 ![image-20260404195343563](./README.assets/image-20260404195343563.png) 手动运行时候,就可以选择yaml环境即可 ![image-20260404195439456](./README.assets/image-20260404195439456.png) ### 提交代码自动推送 源码构建配置。在配置Git网站中的webhook。提交代码至Git后,就可以自动触发代码执行。 ![image-20260404195718747](./README.assets/image-20260404195718747.png) ![image-20260404195903425](./README.assets/image-20260404195903425.png) ![image-20260404200550380](./README.assets/image-20260404200550380.png) ### 配置shell 用来安装环境依赖与清理历史数据。 检查容器是否安装Redis、MySql。安装依赖包 ~~~shell #!/bin/bash # ========================================== # 自动化测试框架 # ========================================== # 设置错误处理和日志合并 set -e exec 2>&1 echo "==========================================" echo "🚀 开始构建自动化测试任务" echo "📂 工作空间: ${WORKSPACE}" echo "🔢 构建编号: ${BUILD_NUMBER}" echo "🌍 目标测试环境: ${AUTO_ENV}" # 这里会打印您在参数化构建中选择的 test7.yaml 或 prod.yaml echo "⏰ 开始时间: $(date)" echo "==========================================" # 切换到工作目录 cd "${WORKSPACE}" # ------------------------------------------ # [1/5] 启动并检查 Redis 缓存中间件 # ------------------------------------------ echo -e "\n▶ [1/5] 启动中间件 (Redis)..." if command -v redis-server &> /dev/null; then # 清理遗留的僵尸进程 pkill -f redis-server 2>/dev/null || true sleep 1 # 后台启动 Redis redis-server --daemonize yes sleep 2 if redis-cli ping 2>/dev/null | grep -q PONG; then echo "✅ Redis 启动成功" else echo "⚠️ Redis 启动失败,继续执行..." fi else echo "❌ 致命错误: 当前 Jenkins 服务器未安装 Redis!" exit 1 fi # ------------------------------------------ # [2/5] 装载 Python 独立虚拟环境 # ------------------------------------------ echo -e "\n▶ [2/5] 装载 Python 运行环境..." if [ -d "venv" ] && [ -f "venv/bin/activate" ]; then source venv/bin/activate echo "✅ 成功激活已有虚拟环境 (.venv)" else echo "⏳ 首次运行,正在创建全新虚拟环境..." python3 -m venv venv source venv/bin/activate fi # 将虚拟环境路径注入全局环境变量,防止找不到 pytest 等命令 export PATH="${WORKSPACE}/venv/bin:${PATH}" # 智能依赖安装:只有当 requirements.txt 发生修改时,才会触发重新安装,极大地提升构建速度 if [ -f "requirements.txt" ]; then if [ ! -f "venv/.deps_installed" ] || [ "requirements.txt" -nt "venv/.deps_installed" ]; then echo "📦 检测到依赖清单有更新,正在同步安装第三方库..." pip install -r requirements.txt --upgrade --quiet touch venv/.deps_installed echo "✅ 依赖同步完成" else echo "⚡ 依赖清单无变化,触发缓存极速跳过" fi fi # ------------------------------------------ # [3/5] 初始化测试与报告目录 # ------------------------------------------ echo -e "\n▶ [3/5] 清理历史报告残骸..." if [ -d "reports/temps" ]; then # 仅清理上一次留下的 json/txt/xml 结果,避免污染本轮 Allure 报告 find reports/temps -maxdepth 1 -type f \( -name "*.json" -o -name "*.txt" -o -name "*.xml" \) -delete 2>/dev/null || true echo "🧹 历史临时数据清理完毕" else mkdir -p reports/temps fi # ------------------------------------------ # [4/5] 🚀 执行自动化测试核心引擎 # ------------------------------------------ echo -e "\n▶ [4/5] 开始执行测试流水线..." EXIT_CODE=0 # 向框架内注入 Jenkins 特有环境变量 (用于消息通知拼装 URL) export JENKINS_BUILD_NUMBER="${BUILD_NUMBER}" export JENKINS_BUILD_URL="${BUILD_URL}" export JENKINS_JOB_NAME="${JOB_NAME}" # 注:AUTO_ENV 已经在 Jenkins 参数化构建时自动注入到了系统环境中 if [ -f "run.py" ]; then echo "🎯 触发入口: python run.py" # 加入 30 分钟 (1800秒) 超时熔断机制,防止死循环卡死整个 Jenkins 节点 # || EXIT_CODE=$? 确保即使有部分用例失败(退出码不为 0),脚本也不会中断,而是让后面的报告正常生成 timeout 1800 python run.py || EXIT_CODE=$? else echo "⚠️ 未找到 run.py,使用原生 pytest 兜底执行..." pytest testcase/ -v --alluredir=reports/temps --disable-warnings || EXIT_CODE=$? fi echo "📊 测试执行完毕,底层退出状态码: ${EXIT_CODE}" # ------------------------------------------ # [5/5] 收尾打扫与环境重置 # ------------------------------------------ echo -e "\n▶ [5/5] 执行退出回收协议..." # 1. 关闭临时拉起的 Redis 服务 pkill -f redis-server 2>/dev/null || true # 2. 检查 Allure 结果是否成功落地 if [ -d "reports/temps" ]; then JSON_COUNT=$(find reports/temps -name "*.json" 2>/dev/null | wc -l) echo "📁 本轮共生成 Allure JSON 数据切片: ${JSON_COUNT} 个" fi if [ ${EXIT_CODE} -eq 0 ]; then echo "🎉 完美!所有接口测试全部通过!" else echo "🔴 警告:存在失败的用例或断言,请前往 Allure 报告查看详情!" fi echo "==========================================" echo "🏁 构建流水线运行结束" # 极客细节:透传底层状态码! # 如果测试有失败,这里的 exit 会把错误码传给 Jenkins,这样 Jenkins 的球才会变成红色/黄色,而不是假装成功的蓝色。 exit ${EXIT_CODE} ~~~ ![image-20260404200024236](./README.assets/image-20260404200024236.png) 构建后查看Allure报告。记得按照插件啊 ![image-20260404200117909](./README.assets/image-20260404200117909.png)