在接口自动化测试领域,Pytest 和 Requests 是 Python 生态中组合频率非常高的一对工具。Requests 负责把 HTTP 请求发出去并拿到响应,Pytest 负责把用例组织起来、执行断言、生成报告。很多人搭框架时最困惑的不是某一条用例怎么写,而是整个工程该按什么结构组织:配置放哪里、公共方法放哪里、测试数据怎么管理、日志怎么落盘、环境切换怎么做。这篇文章会从一个最小可用用例开始,逐步搭出一个适合中小团队使用的接口自动化测试框架,并给出常见报错的排查路径。
1. 先理清 Pytest 和 Requests 在接口自动化框架里各自负责什么
接口自动化测试框架并不是“Requests 加 Pytest”两个库拼在一起这么简单。先搞清楚两个库的边界,后面设计目录和封装时才不会混淆职责。
1.1 Requests 让 HTTP 请求发送变得可控
Requests 是一个 HTTP 客户端库,解决的是“如何把一个 HTTP 请求发送出去,并把响应读回来”的问题。它不关心测试用例是否通过,也不负责生成测试报告。
最基本的用法是这样:
import requests resp = requests.post( "https://httpbin.org/post", json={"username": "tester", "password": "123456"}, timeout=10, ) print(resp.status_code) print(resp.json())这段代码能做三件事:
- 构造一个 POST 请求,并指定 JSON 请求体。
- 等待服务端返回响应。
- 把响应状态码和 JSON 内容打印出来。
但它没有断言,没有测试组织能力,也不会告诉你“这条用例失败在哪一步”。也就是说,只靠 Requests 写出来的叫“请求脚本”,不叫“测试框架”。
Requests 在框架中的定位是协议层。它负责统一管理请求头、超时时间、连接复用、Cookie 和认证信息。这一层做得好,用例层就不需要每次请求都重复写requests.post(...)。
1.2 Pytest 提供测试组织和断言能力
Pytest 是测试框架,负责把请求脚本变成可执行的测试用例。它提供四个核心能力:
- 用例识别:文件名、函数名、类名满足规则时自动收集。
- 断言:直接使用 Python 的
assert,失败时输出详细信息。 - 夹具:
fixture提供初始化、清理、共享对象的能力。 - 插件:通过 pytest-html、pytest-rerunfailures、allure-pytest 扩展报告和重试能力。
最小示例:
def test_add(): assert 1 + 1 == 2运行:
pytest -vPytest 会找到test_开头的函数并执行。断言失败时,它会显示表达式两边的值,方便定位问题。
在接口自动化中,Pytest 负责的是用例层和调度层:谁先执行、哪些用例需要登录态、哪些用例属于冒烟集、失败后是否重试、最终生成什么报告。Requests 不关心这些,Pytest 也不关心 HTTP 细节,两者配合才完整。
1.3 脚本和框架的本质区别
单脚本的问题在于所有逻辑堆在一起。写三条用例时还能忍受,写到几十条用例时会出现明显痛点:环境地址写死、数据无法复用、没有日志、失败后不知道请求到底发出去没有、换一套环境要改所有代码。
框架化则是把职责拆成层次:
| 层次 | 职责 | 典型模块 |
|---|---|---|
| 用例层 | 描述业务场景,断言响应结果 | testcases/ |
| 业务封装层 | 提供登录、下单等场景方法 | core/ |
| 协议层 | 统一请求发送、超时、请求头 | core/api_client.py |
| 数据层 | 管理测试数据和环境配置 | data/、config/ |
| 报告层 | 日志、报告、执行结果汇总 | pytest 插件 |
框架化不是引入多少高级概念,而是让每段代码只做一件事。后面所有章节都围绕这个分层展开。
2. 环境准备与项目骨架:目录结构决定框架能不能扩散
接口自动化框架的目录结构不是形式主义。目录分得不清楚,配置、数据、用例、公共模块最终会混在一起,项目越大越难维护。
2.1 Python 版本、虚拟环境和依赖管理
建议使用 Python 3.9 及以上版本。Pytest、Requests 对 Python 3.9 以上版本支持稳定,类型注解和 f-string 等特性也更好用。
创建虚拟环境:
python -m venv .venv激活虚拟环境:
# Linux / macOS source .venv/bin/activate # Windows .venv\Scripts\activate激活后,命令行提示符前会出现(.venv)。这一步很重要,接口自动化环境的依赖必须和系统其他项目隔离,否则不同项目对同一依赖的版本要求会互相干扰。
2.2 推荐目录结构
推荐按以下结构组织项目:
api_test/ ├── config/ │ ├── config.yaml │ └── config.py ├── core/ │ └── api_client.py ├── data/ │ └── login_cases.yaml ├── testcases/ │ ├── conftest.py │ └── test_login.py ├── utils/ │ ├── logger.py │ └── yaml_loader.py ├── requirements.txt ├── pytest.ini └── run.py各目录职责:
| 目录或文件 | 职责 |
|---|---|
| config/ | 环境配置文件和配置读取类 |
| core/ | 请求封装、核心客户端 |
| data/ | 接口测试数据文件 |
| testcases/ | 测试用例和 conftest.py |
| utils/ | 日志、文件读取等工具 |
| requirements.txt | 依赖列表 |
| pytest.ini | Pytest 配置 |
| run.py | 可选,统一执行入口 |
这个结构的特点是:用例层不直接依赖 Requests 细节,config 层不关心用例内容,数据文件只描述输入输出。后续扩展 CI、新增接口、切换环境都只改对应层。
2.3 requirements.txt 依赖说明
在项目根目录创建requirements.txt:
requests==2.32.3 pytest==8.2.1 PyYAML==6.0.1 pytest-html==4.1.0 pytest-rerunfailures==14.0安装:
pip install -r requirements.txt这里把版本写死,是为了保证团队和 CI 环境执行结果一致。如果原始安装时不知道具体版本,可以先不写版本号安装,再用pip freeze > requirements.txt生成锁定版本。
常用依赖用途:
| 依赖 | 用途 | 说明 |
|---|---|---|
| requests | 发送 HTTP 请求 | 框架核心 |
| pytest | 测试框架 | 用例组织与执行 |
| PyYAML | 解析 YAML 数据 | 存储配置和测试数据 |
| pytest-html | 生成 HTML 报告 | 方便本地查看 |
| pytest-rerunfailures | 失败用例重试 | 处理偶发网络问题 |
2.4 最小环境校验
创建最小用例文件testcases/test_demo.py:
def test_demo_ok(): assert 1 == 1执行:
pytest testcases/test_demo.py -v预期输出包含:
testcases/test_demo.py::test_demo_ok PASSED如果这一步失败,先不要继续写用例,优先检查 Python 解释器是否来自虚拟环境、依赖是否装全、路径是否进入项目根目录。环境没跑通之前,后面所有框架代码都无法验证。
3. 从最小用例到框架化:配置、封装与夹具
很多教程直接给一整套封装代码,读者复制下来却不知道为什么要这样分。这一章从一条最原始的 Requests 用例开始,一步步改成框架结构。
3.1 用 Requests 直接写一条用例
先写一条没有封装、没有配置的原始用例:
import requests def test_login(): resp = requests.post( "https://httpbin.org/post", json={"username": "tester", "password": "123456"}, timeout=10, ) assert resp.status_code == 200 assert resp.json()["json"]["username"] == "tester"这条用例能跑通,但有三处不好维护:
base_url写死在用例里,换环境要改代码。- 超时时间写死,不能在配置中统一调整。
- 每个接口都要手动调
requests.post,如果后续要统一加日志、加请求头,需要改所有用例。
3.2 fixture 管理 base_url 和 Session
先用 Pytest fixture 把客户端提取出来。这里引入requests.Session(),它比直接调用requests.post更有优势:
- 复用底层 TCP 连接,请求效率更高。
- 可以统一保存 Cookie,登录态不用每次手动处理。
- 可以统一设置 headers,例如
Authorization。
改进后的 conftest.py:
import pytest import requests @pytest.fixture(scope="session") def client(): session = requests.Session() session.base_url = "https://httpbin.org" yield session session.close() def test_login(client): resp = client.post( client.base_url + "/post", json={"username": "tester", "password": "123456"}, timeout=10, ) assert resp.status_code == 200 assert resp.json()["json"]["username"] == "tester"scope="session"表示整个测试会话只创建一次客户端。如果每个用例都创建一个新 Session,就失去了连接复用和登录态共享的意义。
但这里仍有问题:base_url还是硬编码在 fixture 里。下一步引入配置文件。
3.3 配置管理:用 YAML 支持多环境切换
创建config/config.yaml:
test: base_url: "https://httpbin.org" timeout: 10 prod: base_url: "https://api.example.com" timeout: 15创建config/config.py:
from pathlib import Path import yaml class Config: def __init__(self, env="test"): config_path = Path(__file__).parent / "config.yaml" with open(config_path, "r", encoding="utf-8") as f: self._data = yaml.safe_load(f) if env not in self._data: raise ValueError(f"unknown env: {env}") self._env = env @property def base_url(self): return self._data[self._env]["base_url"] @property def timeout(self): return self._data[self._env]["timeout"]这样配置读取被统一收口。以后新增环境,只增加 YAML 对应节点,不需要改用例。
实际生产项目还可以增加环境变量覆盖逻辑:
import os env = os.getenv("API_ENV", "test") config = Config(env)API_ENV是约定名称,CI 里可以通过环境变量注入test、staging或prod。配置文件的职责是提供默认值,环境变量负责覆盖运行时环境。
3.4 统一请求封装:日志、超时与异常兜底
有了配置,还需要一个统一的请求客户端,让用例层不再关心 URL 拼接和超时设置。
创建core/api_client.py:
import logging import requests from config.config import Config logger = logging.getLogger("api") class ApiClient: def __init__(self, env=None): self.config = Config(env) self.session = requests.Session() self.base_url = self.config.base_url def request(self, method, path, **kwargs): url = path if path.startswith("http") else self.base_url + path kwargs.setdefault("timeout", self.config.timeout) logger.info("request: %s %s", method.upper(), url) logger.info("params: %s", kwargs.get("params")) resp = self.session.request(method, url, **kwargs) logger.info( "response status: %s, body: %s", resp.status_code, resp.text[:1000], ) return resp def get(self, path, **kwargs): return self.request("GET", path, **kwargs) def post(self, path, **kwargs): return self.request("POST", path, **kwargs) def close(self): self.session.close()关键点:
setdefault("timeout", ...)表示调用方传了 timeout 就用调用方的值,没传才用配置默认值。- 路径以
http开头时直接使用完整地址,否则拼接base_url。 - 日志记录请求方法和 URL,响应体只记录前 1000 个字符,避免大响应刷屏。
改造后的用例:
from core.api_client import ApiClient def test_login(client): resp = client.post("/post", json={"username": "tester", "password": "123456"}) assert resp.status_code == 200 assert resp.json()["json"]["username"] == "tester"现在用例层不再关心环境地址、超时、连接管理,只描述“发什么请求、断言什么结果”。
4. 把测试数据从用例代码中剥离出来:参数化与数据驱动
接口自动化框架做到一定程度,瓶颈通常不在请求发送,而在用例数据的组织和维护。把数据从用例代码里剥离,是降低维护成本的关键。
4.1 parametrize 参数化的三种常见用法
最简单的参数化是直接在函数上标记:
import pytest @pytest.mark.parametrize("username,password,expected", [ ("tester", "123456", 200), ("", "123456", 200), ]) def test_login_param(client, username, password, expected): resp = client.post("/post", json={"username": username, "password": password}) assert resp.status_code == expectedPytest 会把每个元组生成一条独立用例,失败时只看对应数据即可。
第二种用法是传入列表,适合参数本身就是字典:
@pytest.mark.parametrize("payload", [ {"username": "tester", "password": "123456"}, {"username": "tester2", "password": "password"}, ]) def test_login_payload(client, payload): resp = client.post("/post", json=payload) assert resp.status_code == 200第三种用法是给用例起别名:
@pytest.mark.parametrize("username,password", [ ("tester", "123456"), ("tester2", "password"), ], ids=["normal_user", "second_user"]) def test_login_ids(client, username, password): passids只影响测试报告中的用例名,不影响执行逻辑。数据量大时建议使用,方便在报告中定位失败数据。
4.2 用 YAML 文件管理接口用例数据
参数写在函数上仍然不方便维护,尤其是用例数据多、需要和环境一起变更时。推荐把数据放到 YAML 文件。
创建data/login_cases.yaml:
cases: - name: "登录成功" path: "/post" payload: username: "tester" password: "123456" expected: status_code: 200 - name: "用户名为空" path: "/post" payload: username: "" password: "123456" expected: status_code: 200创建utils/yaml_loader.py:
from pathlib import Path import yaml def load_cases(file_name): data_path = Path(__file__).parent.parent / "data" / file_name with open(data_path, "r", encoding="utf-8") as f: data = yaml.safe_load(f) return data["cases"]用例文件改造:
import pytest from utils.yaml_loader import load_cases @pytest.mark.parametrize("case", load_cases("login_cases.yaml")) def test_login(client, case): resp = client.post(case["path"], json=case["payload"]) assert resp.status_code == case["expected"]["status_code"]这里有个容易忽略的坑:parametrize在收集阶段就会执行load_cases("login_cases.yaml")。如果 YAML 文件路径写错,整个测试模块会收集失败,而不是执行失败。所以数据文件路径一定要和项目根目录对应,建议在conftest.py里把项目根目录注入sys.path,避免不同运行目录导致的导入问题。
4.3 conftest.py 是公共 fixture 的注册中心
conftest.py可以被同目录及子目录下的测试用例自动加载。公共 fixture 放这里,不需要每个测试文件 import。
扩展后的testcases/conftest.py:
import pytest from core.api_client import ApiClient from utils.logger import setup_logger @pytest.fixture(scope="session") def client(): setup_logger() c = ApiClient("test") yield c c.close()如果配置支持--env参数,可以用pytest_addoption处理:
def pytest_addoption(parser): parser.addoption("--env", action="store", default="test", help="test or prod") @pytest.fixture(scope="session") def env(request): return request.config.getoption("--env") @pytest.fixture(scope="session") def client(env): c = ApiClient(env) yield c c.close()这里client依赖env,Pytest 会自动先准备env。命令行运行时:
pytest --env test pytest --env prod注意:client是session作用域,整个测试会话只创建一次。如果某条用例修改了共享 Session 的 headers,后续用例会受到影响。这是框架设计时就要考虑的问题。
4.4 响应断言:不要对着文本字符串做精确匹配
接口断言最常见的问题是:
assert "username" in resp.text这种写法非常脆弱。响应体可能因为字段顺序、空格、换行、转义字符变化导致误判,而且 JSON 里的值即使错误只要字符串片段存在也会通过。
推荐对 JSON 字段断言:
def test_login_json(client): resp = client.post("/post", json={"username": "tester", "password": "123456"}) data = resp.json() assert data["json"]["username"] == "tester"更稳妥的做法是封装统一断言函数:
def assert_json_field(resp, field_path, expected): data = resp.json() current = data for part in field_path.split("."): current = current[part] assert current == expected, f"{field_path} expected {expected}, got {current}"使用:
assert_json_field(resp, "json.username", "tester")这里要注意 YAML 数据文件的类型问题。YAML 中true会被解析为布尔值,001会被解析为数字1,如果接口期望的是字符串"001",必须在 YAML 中加引号:
payload: phone: "001"否则断言会莫名其妙失败,而且不容易察觉。
5. 日志、测试报告与失败重试:让接口自动化变成可观测的工程
接口自动化用例一旦跑起来,最怕的问题就是“为什么失败”。没有日志,没有报告,没有重试策略,排查只能靠猜。这一章解决可观测性。
5.1 logging 配合 Requests 记录请求和响应
创建utils/logger.py:
import logging import sys def setup_logger(name="api", level=logging.INFO): logger = logging.getLogger(name) if not logger.handlers: handler = logging.StreamHandler(sys.stdout) fmt = logging.Formatter("%(asctime)s %(levelname)s %(name)s %(message)s") handler.setFormatter(fmt) logger.addHandler(handler) logger.setLevel(level) return logger在conftest.py中调用一次:
from utils.logger import setup_logger setup_logger()日志会打印到控制台,同时可以配置 pytest 的日志输出:
[pytest] log_cli = true log_cli_level = INFO这里注意不要重复添加 handler。日志对象是全局的,如果每个用例都调用setup_logger并且不加判断,会产生重复日志。上面代码中if not logger.handlers就是防止重复。
5.2 HTML 报告和 Allure 报告的接入方式
在pytest.ini中配置 pytest-html:
[pytest] addopts = -q --html=report.html --self-contained-html testpaths = testcases--self-contained-html会把 CSS 和 JS 合并到单个 HTML 文件,方便发送和归档。运行后生成report.html,直接用浏览器打开。
如果团队要更丰富的报告,可以接入 Allure:
pip install allure-pytest运行时:
pytest --alluredir=allure-results然后用 Allure 命令行生成报告:
allure serve allure-resultsAllure 的优势是按测试套件、用例、步骤展示结果,适合长期存档和团队协作。但搭建成本比 pytest-html 高,需要安装 Java 运行环境和 allure 命令行工具。学习环境建议先用 pytest-html,有精力再迁移 Allure。
5.3 失败重试:什么时候该重试,什么时候不该
接口自动化最常见的偶发失败原因是网络抖动、测试环境服务重启、依赖接口暂时不可用。这时可以使用 pytest-rerunfailures。
在pytest.ini配置:
addopts = -q --reruns 2 --reruns-delay 1或者在用例上单独标记:
import pytest @pytest.mark.flaky(reruns=2, reruns_delay=1) def test_login(client): resp = client.post("/post", json={"username": "tester", "password": "123456"}) assert resp.status_code == 200重试必须“受控”。建议遵守三条规则:
- 重试次数限制在 2 到 3 次,不要无限重试。
- 重试延迟至少 1 秒,避免请求风暴。
- 断言失败、业务逻辑错误、鉴权失败不要重试,重试只会掩盖真实问题。
如果配置了全局--reruns,建议针对关键用例使用--strict-markers和指定 mark,避免所有用例都被无差别重试。
5.4 429 too many requests 的工程化处理
在真实接口测试中,经常看到类似日志:
exceeded retry limit, last status: 429 too many requests这个报错表示请求频率超过服务端限制。429 是 HTTP 标准状态码,服务端通过它告诉调用方:请放慢速度。
遇到 429,第一反应不应该是“把这个报错隐藏掉”,而是按以下顺序处理:
- 检查是否有循环请求或用例并发过高。如果使用了 pytest-xdist 的
-n参数,先把并发数调小。 - 增加退避重试。pytest-rerunfailures 支持延迟重试:
pytest --reruns 3 --reruns-delay 5如果服务端返回了
Retry-After响应头,要优先遵守这个时间。Requests 可以通过resp.headers.get("Retry-After")读取,然后等待对应秒数再继续。检查是否发送了重复请求或者存在未结束的上一次任务。限流往往是短时间内请求数骤增导致的,先处理代码逻辑问题,再考虑重试。
这里尤其要注意:不要把重试写成无限循环,也不要通过绕过频率检测的方式强行请求。合规的做法是接受服务端限流策略,调整测试节奏。如果测试必须高频运行,应该和接口提供方确认测试环境和额度,而不是在脚本层硬扛。
6. 常见报错排查:从现象倒推根因
接口自动化框架跑起来之后,报错会集中出现在几个位置。这一章按“现象 → 可能原因 → 检查方式 → 解决方案”的方式整理。
6.1 fixture 作用域不对导致用例互相污染
现象:单独执行某条用例通过,放在整个测试套件里执行时失败,尤其是登录态相关用例。
原因:client是session作用域,Session 中的 headers、Cookie 是共享状态。前一条用例修改了 headers,后一条用例带着被修改后的状态访问接口。
检查方式:在失败用例前打印client.session.headers,对比单独执行时的差异。
解决方案:不要在用例中直接修改 session 级共享对象。如果某条用例需要特殊请求头,可以在请求方法中临时传入headers,而不是修改client.session.headers。
Pytest fixture 作用域速查:
| 作用域 | 生命周期 | 适用场景 |
|---|---|---|
| function | 每个用例执行一次 | 临时数据、独立数据准备 |
| class | 每个测试类执行一次 | 类级别的准备工作 |
| module | 每个模块执行一次 | 模块内共享资源 |
| session | 整个测试会话执行一次 | 连接、登录态、全局配置 |
6.2 连接超时、SSL 报错和地址配置错误
现象:
requests.exceptions.ConnectionError: Max retries exceeded with url: /post requests.exceptions.SSLError: CERTIFICATE_VERIFY_FAILED requests.exceptions.ConnectTimeout排查顺序:
- 先用
curl检查目标地址是否能通:
curl -i https://httpbin.org/post如果 curl 也失败,说明是网络或服务问题,不是框架问题。
检查 URL 拼接是否正确。封装里如果
base_url末尾带了/,而请求路径又以/开头,会得到/api//post这类地址。建议统一约定base_url不以/结尾,请求路径以/开头。检查超时配置。学习环境可以设置 10 秒,生产环境的读接口建议 5 秒,写接口建议 10 到 15 秒。超时时间太短会误报失败,太长会拖慢整体执行。
遇到
CERTIFICATE_VERIFY_FAILED,不要直接写verify=False逃避。优先确认系统时间、CA 证书、服务端证书链是否正常。测试环境如果确实需要临时忽略证书校验,要在代码中显式注释,并只在测试环境使用。
6.3 断言失败后按什么顺序排查
断言失败不一定代表接口有 bug,也可能请求参数没传对、测试数据过期、环境切换后字段变了。
建议按这个顺序查:
- 状态码是否符合预期。如果状态码是 500,优先看服务端日志,而不是继续看响应体。
- 响应体是否是完整 JSON。某些错误页面返回 HTML,
resp.json()会抛异常。 - 请求参数是否真的发送了。打开日志,确认
params、json、headers字段内容。 - 是否环境问题。同一套用例在 test 环境通过、在 prod 环境失败,说明数据、接口契约或网络策略存在差异。
- 是否是断言表达式问题。例如
assert "username" in resp.text,这种弱断言容易通过,也容易误判。
排查时不要只看最后一行报错。接口自动化最有效的信息是“请求参数 + 响应体 + 日志时间点”,所以框架中日志越详细,排查越快。
6.4 切换环境后用例大面积失败
现象:本地--env test运行正常,CI 里--env prod或--env staging运行大量失败。
可能原因:
config.yaml中没有对应环境节点。- 新环境的域名、端口、鉴权方式不同。
- 测试数据中的数据只存在于 test 环境,prod 环境没有对应数据。
- 环境变量没有正确传入,框架仍然在读取默认环境。
解决方案:
- 配置读取统一走
Config(env),不要在用例里用if env == "prod"写很多分支。 - 环境差异数据按环境拆分文件,例如
data/test/login_cases.yaml和data/prod/login_cases.yaml。 - CI 脚本中显式传环境变量:
export API_ENV=test pytest --env test环境切换失败是框架设计问题,不是用例问题。出现大面积失败时,先检查配置文件和环境变量,再查看具体接口报错,最后才看断言逻辑。
7. 生产环境落地:最佳实践、检查清单与扩展方向
框架在本地跑通只是开始,真正价值体现在团队协作、CI 回归和生产巡检。最后的落地方案直接决定框架能维持多久。
7.1 学习环境、测试环境与生产环境的差异
同一个框架在不同阶段要做不同取舍:
| 维度 | 学习环境 | 测试环境 | 生产环境 |
|---|---|---|---|
| 目标 | 快速跑通 | 回归和联调 | 冒烟和监控 |
| 数据 | 公开接口或本地 mock | 脱敏测试数据 | 专用账号或只读数据 |
| 报告 | 本地 HTML | CI 归档 | 自动归档并通知 |
| 重试 | 可以不配 | 按场景配置 | 必须受控 |
| 安全 | 随意 | 脱敏 | 敏感信息不回显、不打日志 |
| 日志 | 控制台即可 | 文件 + CI 日志 | 日志平台和告警 |
生产环境的接口自动化还要额外考虑:
- 请求不能对业务数据造成污染。
- 调用频率必须控制在接口方允许范围。
- 错误需要告警,而不能只停留在测试报告里。
- 要有回滚机制,压测或长时间回归不要直接在生产环境执行。
7.2 接口自动化框架上线前的检查清单
上线前可以逐项核对:
- 依赖版本已经固定,
requirements.txt可以重复安装。 base_url不在用例代码里写死,全部走配置。- 测试数据独立维护,不依赖用例执行顺序。
- fixture 作用域清晰,没有用例之间共享状态污染。
- 日志能记录请求方法、URL、状态码、响应摘要,但不打印完整 token 和密码。
- 断言不依赖
resp.text字符串包含,按 JSON 字段断言。 - 超时时间已经配置,没有请求被无限挂起。
- 失败重试次数受控,不会掩盖真实失败。
- 报告可在 CI 中生成并归档。
- 敏感信息不进入 Git,配置文件有示例模板。
这些条目看起来简单,但实际项目里每一条都能对应一个真实故障。例如不打印 token,是因为日志一旦上传到日志平台,泄露风险就会放大。断言不依赖文本,是因为响应体字段顺序调整会导致大量误报。
7.3 后续可扩展的方向
框架稳定后,可以从以下方向继续扩展:
- 接入 CI/CD。以 GitLab CI 为例,可以在
.gitlab-ci.yml中增加测试任务:
stages: - test api-test: stage: test script: - pip install -r requirements.txt - pytest --env test artifacts: paths: - report.html登录态管理。用 fixture 统一获取 token,写入 Session headers,而不是每条用例各自登录。
数据隔离。每个环境使用独立测试账号、独立数据文件,避免环境间数据互相影响。
契约校验。在断言之上引入 JSON Schema 或 Schemathesis,校验响应结构是否稳定。
性能回归。接口功能框架不要混入压测逻辑,可以结合 Locust 单独建设性能测试工程,功能回归和性能回归使用不同的数据规模和频率。
如果只记一条,那就是:用例层只写业务表达,把请求发送、配置读取、日志采集和重试策略都下沉到框架层。这样新人接手时不需要关心 HTTP 细节,接口变更时也只需要改数据文件和少量断言。下一步可以从把第一条登录用例跑通开始,再把报告和 CI 接上,框架的价值会在每次回归时体现出来。