
这次我们要解决一个很实际的问题接口自动化测试框架怎么搭。很多测试同学手上有 pytest 基础也会用 requests 写脚本但要把两者整合成一套能复用、能跑批、能出报告的框架时经常卡在项目结构、数据驱动、登录态管理、报告集成这些环节。这篇文章就以Pytest Requests为核心完整演示一套接口自动化测试框架从零搭建、逐步完善到落地执行的全过程。文章不会只给代码片段而是按照真实项目的演进路径来写先搭起最小可运行骨架再逐步加入配置管理、接口封装、数据驱动、日志收集、Allure 报告、批量执行策略。每加一层都会给出可以直接复制的代码、运行方式和验证方法。适合正在做接口测试脚本但没有系统化整理的人也适合想把现有脚本改造成测试框架的团队。1. 核心能力速览能力项说明框架基石Pytest Requests Allure PyYAML Loguru接口请求能力基于 requests.Session 统一管理请求、Cookie、Headers测试执行方式pytest 命令行、pytest.ini 配置、批量执行数据驱动YAML / JSON / Excel 外置测试数据配合 pytest.mark.parametrize登录态方案封装 token 获取与 session 注入支持多环境切换断言方式基于 pytest.assume 实现软断言支持状态码和业务字段校验报告输出Allure 报告包含步骤、日志、截图信息附加信息自动重试使用 pytest-rerunfailures 处理临时网络抖动适合场景接口回归测试、CI/CD 集成、批量接口验证、团队协作运行环境Windows / macOS / LinuxPython 3.8 及以上这套框架的核心思路是测试逻辑和测试数据分离接口操作统一封装用例通过 pytest 组织最后用 Allure 把执行结果可视化。搭建完成后新增一条业务接口用例只需要写测试函数和对应的测试数据不重复写请求代码。2. 适用场景与使用边界2.1 适合做什么这套框架最擅长的场景主要有四类单接口的入参校验比如一个查询接口要验证必填字段、字段类型、边界值、异常值用 parametrize 直接把多组数据喂给同一个测试函数。多接口的业务链路比如下单流程需要先登录、再创建订单、再查询订单这就要依赖 fixture 做步骤间的数据传递。批量接口回归版本发布前把核心接口全部拉出来跑一遍检查有没有接口返回异常、字段变更、鉴权失效。CI/CD 流水线接入Jekins 或 GitLab CI 里执行pytest命令生成 Allure 报告发布到报告服务器。2.2 不适合做什么接口自动化没法替代所有测试工作。以下场景不建议硬套强 UI 交互验证接口测不了页面点击、浏览器渲染、前端报错提示这类必须交给 UI 自动化或手工测试。高并发性能压测requests 是同步请求库不适合做压测工具。压测建议用 Locust、JMeter 或 Gatling。复杂加密协议逆向如果接口有高强度加密和签名且没有测试环境解密通道脚本维护成本会非常高。2.3 合规边界接口自动化测试涉及的是测试环境或已授权接口要注意以下边界只能对你有权测试的系统发起请求不能对未授权系统做扫描、遍历或压力测试。测试数据要脱敏不要用真实用户手机号、身份证号、银行卡号写入代码或测试报告。测试过程中产生的日志、报告、数据文件要按公司安全规范保存不要随意传到公共平台。3. 环境准备与前置条件3.1 操作系统与 Python 版本开发环境以 Windows 11 为例macOS 和 Linux 同样兼容。Python 建议使用 3.9 到 3.12 之间的版本不建议直接用 3.13部分第三方库的 wheel 包可能还没跟上。检查命令python --version pip --version如果本机同时装了 Python 2记得用python3和pip3区分。3.2 创建虚拟环境虚拟环境是为了避免不同项目的依赖冲突。在项目根目录执行# 创建虚拟环境 python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate激活后终端前面会出现(venv)前缀说明已经进入虚拟环境。3.3 安装依赖包先创建requirements.txt写入核心依赖pytest8.2.0 requests2.31.0 PyYAML6.0.1 loguru0.7.2 pytest-rerunfailures14.0 pytest-assume2.9.1 allure-pytest2.13.5执行安装命令pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple这里用的是清华镜像源速度更快。如果公司内部有 PyPI 镜像可以替换成公司地址。安装完成后验证pytest --version python -c import requests; print(requests.__version__)3.4 接口测试标的准备框架需要有一个可测的接口服务。建议优先准备两套目标公司测试环境接口文档比如 Swagger、YApi、Apifox。公网测试接口比如httpbin.org可以用来验证框架本身的正确性。后续示例代码会以https://httpbin.org作为演示目标这部分代码可以直接跑通。4. 项目结构设计与核心代码实现4.1 目录结构一个清晰的项目结构是框架可维护的基础。推荐结构如下api_auto_test/ ├── config/ │ ├── __init__.py │ ├── settings.py │ └── test_data/ │ ├── login_data.yaml │ └── user_data.yaml ├── common/ │ ├── __init__.py │ ├── log_utils.py │ ├── assert_utils.py │ └── request_utils.py ├── core/ │ ├── __init__.py │ └── http_client.py ├── testcases/ │ ├── __init__.py │ ├── conftest.py │ └── test_login.py ├── reports/ │ ├── allure_results/ │ └── allure_report/ ├── logs/ │ ├── info.log │ └── error.log ├── pytest.ini └── requirements.txt目录职责划分config存放环境配置、URL 管理、测试数据文件。common日志、断言、数据读取等通用工具。corerequests.Session 封装、接口基类。testcases测试用例和 conftest.py fixture 定义。reportsAllure 结果和报告。logs日志文件。4.2 配置文件 settings.py配置文件使用全局变量或 dataclass 的方式管理环境和接口地址。推荐使用 dataclass 便于调用和阅读from dataclasses import dataclass, field from pathlib import Path PROJECT_ROOT Path(__file__).resolve().parent.parent dataclass class ENV: env_name: str test base_url: str https://httpbin.org timeout: int 10 max_retries: int 3 # 测试账号实际项目中从环境变量或配置中心读取 username: str admin password: str 123456 env ENV() API_BASE_URL env.base_url实际项目中username和password不应该硬编码可以从环境变量读取import os os.getenv(API_USERNAME, default_user)4.3 日志模块 log_utils.py日志在接口测试排错中非常重要。这里用 loguru 实现简洁且支持按级别输出到不同文件import sys from loguru import logger from config.settings import PROJECT_ROOT log_path PROJECT_ROOT / logs logger.remove() logger.add( sys.stdout, levelINFO, formatgreen{time:YYYY-MM-DD HH:mm:ss}/green | level{level}/level | {message} ) logger.add( log_path / info.log, levelINFO, rotation10 MB, retention7 days, encodingutf-8 ) logger.add( log_path / error.log, levelERROR, rotation10 MB, retention30 days, encodingutf-8 )4.4 HTTP 客户端封装 http_client.pyrequests.Session 会保持 Cookie这对需要登录态的接口测试非常关键。封装一层后接口请求、日志记录、超时处理都集中在一个地方import requests import time import logging from config.settings import env from common.log_utils import logger class HttpClient: def __init__(self, base_urlenv.base_url): self.session requests.Session() self.base_url base_url self.session.headers.update({ User-Agent: Pytest-AutoTest/1.0 }) def request(self, method, url, **kwargs): url self.base_url url kwargs.setdefault(timeout, env.timeout) start_time time.time() resp self.session.request(method, url, **kwargs) elapsed round(time.time() - start_time, 3) logger.info(f请求: {method.upper()} {url}) logger.info(f参数: {kwargs.get(params, )}) logger.info(fBody: {kwargs.get(json, kwargs.get(data, ))}) logger.info(f状态码: {resp.status_code}, 耗时: {elapsed}s) if resp.status_code 400: logger.error(f响应内容: {resp.text}) return resp def get(self, url, **kwargs): return self.request(GET, url, **kwargs) def post(self, url, **kwargs): return self.request(POST, url, **kwargs) def put(self, url, **kwargs): return self.request(PUT, url, **kwargs) def delete(self, url, **kwargs): return self.request(DELETE, url, **kwargs) http_client HttpClient()4.5 断言工具 assert_utils.py接口断言一般分两层HTTP 状态码断言和业务字段断言。为提高用例可读性封装常用断言方法import pytest class AssertUtils: staticmethod def assert_status_code(resp, expected200): 断言HTTP状态码 assert resp.status_code expected, \ f状态码不一致, 期望{expected}, 实际{resp.status_code}, 响应:{resp.text[:500]} staticmethod def assert_json_field(resp, field, expected): 断言JSON字段值 data resp.json() actual data.get(field) assert actual expected, \ f字段 {field} 断言失败, 期望 {expected}, 实际 {actual} staticmethod def assert_in_text(resp, text): 断言响应文本包含某个字符串 assert text in resp.text, f响应中未找到预期文本: {text} staticmethod def soft_assert_json_field(resp, field, expected): 软断言断言失败不中断用例 with pytest.assume: data resp.json() actual data.get(field) assert actual expected, \ f字段 {field} 断言失败, 期望 {expected}, 实际 {actual} assert_utils AssertUtils()4.6 测试数据读取 data_utils.py测试数据放 YAML 文件里用 PyYAML 读取。这样测试用例和输入数据分离后续维护测试数据不需要改代码import yaml from pathlib import Path from config.settings import PROJECT_ROOT def load_yaml_data(file_name): file_path Path(PROJECT_ROOT) / config / test_data / file_name with open(file_path, r, encodingutf-8) as f: data yaml.safe_load(f) return data def load_test_cases(file_name, case_keyNone): data load_yaml_data(file_name) if case_key: return data.get(case_key, []) return data5. 功能测试与效果验证5.1 接口请求基础功能验证先写一条最简单的用例验证 HTTP 客户端封装能不能正常工作。创建testcases/test_demo.pyimport pytest from core.http_client import http_client from common.assert_utils import assert_utils pytest.mark.smoke def test_get_ip(): 验证 GET 请求 resp http_client.get(/ip) assert_utils.assert_status_code(resp, 200) assert origin in resp.text pytest.mark.smoke def test_post_json(): 验证 POST JSON 请求 payload {name: pytest, type: test} resp http_client.post(/post, jsonpayload) assert_utils.assert_status_code(resp, 200) data resp.json() assert data[json][name] pytest pytest.mark.parametrize(params, [ {page: 1, size: 10}, {page: 2, size: 20}, ]) def test_get_query_params(params): 验证 GET 带查询参数 resp http_client.get(/get, paramsparams) assert_utils.assert_status_code(resp, 200) assert resp.json()[args][page] str(params[page])运行命令pytest testcases/test_demo.py -v预期输出会显示每条用例的 PASS 或 FAIL 状态日志中会输出请求地址、参数、响应状态码。5.2 数据驱动测试数据驱动是接口自动化测试框架的核心能力之一。以登录接口为例定义一个login_data.yamlvalid_login: - case: 正确账号密码 payload: username: admin password: 123456 expected_code: 200 expected_msg: success invalid_login: - case: 空用户名 payload: username: password: 123456 expected_code: 422 - case: 错误密码 payload: username: admin password: wrong expected_code: 401测试用例import pytest from core.http_client import http_client from common.assert_utils import assert_utils from common.data_utils import load_test_data login_cases load_test_data(login_data.yaml) pytest.mark.parametrize(case, login_cases[valid_login]) def test_login_valid(case): resp http_client.post(/post, jsoncase[payload]) assert_utils.assert_status_code(resp, 200) assert_utils.assert_json_field(resp, json, case[payload])运行pytest testcases/test_demo.py -k valid_login -v这种模式下新增一条用例只需要在 YAML 里加一组数据测试函数代码完全不用动。5.3 conftest.py 与 fixture 应用conftest.py 是 pytest 的全局配置文件可以在这里定义 fixture。常见的 fixture 包括初始化日志生成测试环境配置登录并注入 token统计用例执行数据基础版 conftest.pyimport pytest from common.log_utils import logger from config.settings import env pytest.fixture(scopesession, autouseTrue) def init_log(): logger.info(f测试开始环境{env.env_name}) yield logger.info(测试结束) pytest.fixture(scopesession) def base_url(): return env.base_url pytest.fixture(scopeclass) def login_token(): 模拟登录获取token resp http_client.post(/post, json{ username: env.username, password: env.password }) assert_utils.assert_status_code(resp, 200) token mock_token_ str(time.time()) return token5.4 登录态与 Session 保持接口自动化测试中很多接口需要登录态。requests.Session 会自动保存 Cookie但很多系统使用的是 Token 而非 Cookie需要在 Header 中传递。此时可以在请求封装中加入 token 管理from common.log_utils import logger class TokenManager: _token None classmethod def set_token(cls, token): cls._token token logger.info(f设置Token: {token}) classmethod def get_token(cls): return cls._token在 conftest.py 的 fixture 中获取 token 后注入到 HttpClientpytest.fixture(scopesession, autouseTrue) def init_token(): resp http_client.post(/post, json{ username: env.username, password: env.password }) token resp.json().get(data, {}).get(token) if not token: token mock_token_for_demo TokenManager.set_token(token) http_client.session.headers.update({Authorization: fBearer {token}}) logger.info(Token已注入Session)6. 批量任务与执行策略配置6.1 pytest.ini 配置pytest.ini 是 pytest 的配置入口可以把常用参数固化在配置文件里避免每次执行都要输入一长串参数[pytest] addopts -v -s --strict-markers --alluredirreports/allure_results testpaths testcases markers smoke: 冒烟测试用例 regression: 回归测试用例 login: 登录模块用例 user: 用户模块用例-v显示详细执行信息。-s显示 print 输出。--strict-markers使用自定义标记避免拼错。--alluredir指定 Allure 结果目录。6.2 按标记批量执行需要冒烟测试时pytest -m smoke需要回归测试时pytest -m regression需要执行登录模块时pytest -m login标记机制配合 CI 流水线非常灵活后续 Jenkins 里只需要一行命令就能切换执行范围。6.3 用例执行顺序控制pytest 默认按文件顺序执行但业务链路用例往往有依赖关系。使用pytest-ordering控制顺序pip install pytest-orderingimport pytest pytest.mark.run(order1) def test_login(): ... pytest.mark.run(order2) def test_create_order(): ... pytest.mark.run(order3) def test_query_order(): ...6.4 执行失败自动重试接口测试经常遇到偶发性的网络抖动、服务超时这类失败会导致误报。配置pytest-rerunfailurespip install pytest-rerunfailures在 pytest.ini 中加入addopts -v -s --reruns 2 --reruns-delay 1 --alluredirreports/allure_results表示失败后重试 2 次间隔 1 秒。断言失败不会自动重试只有用例级异常才会触发。日志中会标注Rerun信息。6.5 多进程并行执行用例较多时可以用pytest-xdist加速pip install pytest-xdist# 4个进程并行执行 pytest -n 4并行执行要注意测试数据隔离。如果多个进程同时写同一个数据库字段可能互相污染。稳妥的做法是不同进程跑不同的模块或用独立测试数据。7. 日志收集与 Allure 报告7.1 在用例中记录业务日志日志不只是代码运行输出更要在用例中记录关键断言信息。例如登录用例的封装import pytest from core.http_client import http_client from common.assert_utils import assert_utils from common.log_utils import logger from common.token_manager import TokenManager class TestAuth: pytest.mark.login def test_login_success(self): payload {username: admin, password: 123456} resp http_client.post(/post, jsonpayload) logger.info(f登录接口响应: {resp.json()}) assert_utils.assert_status_code(resp, 200) assert_utils.assert_json_field(resp, json, payload) token resp.json().get(data, {}).get(token) if token: TokenManager.set_token(token)7.2 Allure 报告集成下载 Allure 命令行工具Windows 可以直接用npm install -g allure-commandline安装macOS 用 Homebrewbrew install allure执行完 pytest 后报告数据在reports/allure_results。要生成 HTML 报告执行allure generate reports/allure_results -o reports/allure_report --clean打开报告allure open reports/allure_report7.3 Allure 步骤与附加信息要让报告更有价值在用例里添加步骤描述和请求响应信息import allure allure.feature(用户模块) allure.story(查询用户) class TestUser: allure.title(查询用户列表-成功) allure.severity(allure.severity_level.CRITICAL) def test_get_user_list(self): with allure.step(发送GET请求): resp http_client.get(/get, params{page: 1}) with allure.step(断言响应状态码): assert_utils.assert_status_code(resp, 200) with allure.step(附加响应信息): allure.attach( bodyresp.text, nameAPI响应, attachment_typeallure.attachment_type.JSON )重新生成报告后可以看到每个用例的执行步骤、请求响应、失败日志。8. 资源占用与性能观察接口自动化测试框架本身对硬件资源要求不高但执行过程中仍要关注几个指标8.1 请求耗时观察在 HttpClient 封装中已经记录了每次请求的耗时。对耗时敏感的接口可以增加超时断言def assert_response_time(resp, max_time3): elapsed resp.elapsed.total_seconds() assert elapsed max_time, f接口响应耗时 {elapsed}s 超过阈值 {max_time}s8.2 避免请求风暴和 429 状态码这里要特别提醒一个常见坑当接口有频控限制时盲目并发或批量循环会导致429 Too Many Requests。热词搜索材料中也有大量exceeded retry limit, last status: 429 too many requests的情况。解决办法在用例之间增加固定间隔如time.sleep(0.5)。使用pytest-rerunfailures对 429 做重试但重试次数要有限制。对不可控的外部接口不要用高并发模式。可以写一个限流包装器import time from functools import wraps def rate_limit(delay0.5): 简单限流装饰器控制请求频率 def decorator(func): wraps(func) def wrapper(*args, **kwargs): time.sleep(delay) return func(*args, **kwargs) return wrapper return decorator8.3 测试数据量增长随着用例增加log 文件、Allure 结果文件会快速膨胀。建议log 文件按天轮转保留 7 天。allure_results 每次执行前清空。报告服务器只保存最近 N 次执行结果。9. 常见问题与排查方法问题现象可能原因排查方式解决方案安装依赖时报 SSL 错误公司网络代理拦截查看 pip 完整报错信息使用公司内部镜像源或配置可信代理pytest 找不到自定义模块项目根目录未加入 sys.path检查 conftest.py 是否存在在 pytest.ini 中配置pythonpath .需 pytest 7用例执行顺序不符合预期fixture scope 设置不当检查 fixture 的 scope 和依赖使用 pytest-ordering 显式控制接口返回 429 Too Many Requests请求频率超过接口频控限制查看响应头 Retry-After增加请求间隔、降低并发数、关闭自动重试响应中文乱码requests 默认编码设置不正确打印响应编码设置resp.encoding utf-8或按接口文档指定编码解析Allure 报告无法生成allure 命令行未安装或未配置 PATH执行allure --version安装 allure-commandline 并配置环境变量登录状态失效token 过期或 session 未复用查看响应状态码和响应体在 fixture 中增加 token 刷新逻辑批量执行时数据库数据冲突并发用例使用相同测试数据检查测试数据隔离方案每个用例使用独立数据源或串行执行断言失败但用例仍通过断言工具没被正确调用检查是否忽略了返回值或没调用断言函数确保使用assert_utils.assert_status_code(resp, 200)而非手写 printpytest 重试不生效rerunfailures 插件未安装或配置不对查看 pytest 启动日志确认依赖安装成功检查--reruns参数10. 最佳实践与使用建议10.1 框架分阶段演进不要一次写完所有功能建议按阶段推进第一阶段requests 发请求 断言 手动执行。第二阶段引入 pytest 管理用例 fixture 处理前置条件。第三阶段YAML 数据驱动 日志 报告。第四阶段批量执行 CI/CD 集成 token 自动刷新。每个阶段都要保证当前用例能 100% 跑通再进入下一阶段。10.2 用例设计建议一个测试函数只测一个业务场景不要在一个函数里堆多个接口。用例命名要表达业务含义如test_add_user_success、test_add_user_missing_required_fields。断言要同时覆盖状态码和关键业务字段不要只断resp.status_code 200。外部依赖接口尽量用 mock 或测试环境夹具替代避免测试结果不稳定。10.3 数据管理规范YAML 数据文件按模块拆分不放在同一个文件里。涉及账号密码等重要数据时从环境变量或配置中心读取。测试数据文件不要包含生产环境真实数据。10.4 并发执行注意事项使用pytest-xdist并行执行时要特别注意登录 fixture 每个 worker 会执行一次相当于多个进程都持有独立 session。如果接口服务在登录时踢掉旧会话那么多个 worker 之间可能互相影响。这种情况下建议关闭 xdist 并行或者按模块拆分到不同的 worker 并隔离用户。10.5 与 CI/CD 集成Jenkins 流水线中的核心步骤# 1. 安装依赖 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple # 2. 执行测试 pytest -m regression --alluredirreports/allure_results # 3. 生成报告 allure generate reports/allure_results -o reports/allure_report --clean # 4. 发布报告 allure open reports/allure_report如果是 GitLab CI路径和缓存的写法要按项目配置调整核心逻辑一致。11. 总结这套 Pytest Requests 接口自动化测试框架没有用到特别高深的技术核心就是做好三件事把请求封装统一、把测试数据外置、把执行结果可视化。从工作量上看搭建最小可用版本一天内可以完成后续主要是用例的沉淀和维护。最值得先验证的功能有两个一是数据驱动确认 YAML 中的测试数据能正确传入用例二是 Allure 报告确认日志、步骤和响应信息能落到报告里。最容易踩的坑是 429 频控处理方式就是控制执行频率不要盲目堆并发。后续扩展方向可以关注 pytest-bdd 做行为驱动、和 DevOps 平台打通做质量门禁、以及把测试数据迁移到数据库或配置中心。这套框架建议作为团队接口测试的基础模板持续积累用例之后它的价值会越来越明显。