☰
用pytest+requests从零搭建接口自动化测试框架
2026/10/7 5:12:55 网站建设 项目流程

刚开始搞接口自动化测试的时候,很多人第一反应是去网上找一套现成的开源框架,下载下来、配好环境、改改参数,感觉就能开跑了。结果用不了两周就会遇到一堆尴尬事:接口一多,用例文件乱成一锅粥;环境一换,所有 base_url 和 token 得手动改一遍;断言写得太死,前端字段顺序一变,整片用例标红。到了这个阶段,你就不得不承认一件事:真正拖后腿的从来不是你要测的接口,而是你那套没经过设计的"脚本集合"。

我做接口自动化测试大概有五六年了,从最初的 Postman 手动点、到 Jmeter 压测脚本、再到自己用 Python 从头搭框架,中间换过不少路子。这篇文章要讲的,是我自己反复迭代后沉淀下来的一个思路——用 pytest + requests 作为底座,从零开始搭建一套接口自动化测试框架。它不一定是最炫的,但一定是我实际用下来最稳、最容易维护、团队协作起来最顺手的方案。

这篇内容适合谁?主要两类人:第一类是准备把接口自动化从"脚本"升级成"框架"的测试工程师,第二类是刚接触测试开发、想了解整个框架全貌的新人。我会按真实的搭建顺序来讲,从选型、目录设计、核心模块封装,一直到数据驱动、鉴权处理、报告输出和 CI 集成。你不需要有多深的 Python 基础,只要能看懂函数和类的写法,跟着思路走就能搭出属于自己的第一版框架。

1. 动手之前先想清楚:这套框架到底要解决什么问题

很多技术人有个习惯:一上来就写代码,写到一半才发现需求根本没想明白。搭建接口自动化框架尤其如此,因为框架本质上是一个"为他人服务的基础设施",你要是连服务对象是谁、他们需要什么都不知道,写出来的东西大概率是自嗨。

1.1 为什么是 pytest + requests,而不是其他组合

选型这件事,我先说说结论,再讲理由。

当时我面前摆着几条路:unittest、pytest、Robot Framework,还有 Java 系的 TestNG + HttpClient。最终选了 pytest + requests,核心考虑有四个:

  • ** pytest 的参数化机制极其灵活**。接口测试最大的痛点是"同一接口、多组数据反复验证",pytest 的@pytest.mark.parametrize装饰器可以很优雅地解决这个问题,而 unittest 的ddt库用起来远没有这么顺手。
  • fixture 解决前置依赖问题。登录拿 token、数据库造数据、清理脏数据,这些操作在 unittest 里要写setUp和tearDown,代码之间靠继承关系绑定,很别扭。pytest 的 fixture 是函数级别的,即用即取,还能按作用域控制执行时机,灵活度完全不同量级。
  • 断言方式贴近直觉。assert response.status_code == 200一行搞定,不像 Java 系要调用各种Assert.assertEquals()。写起来快,读起来也舒服。
  • 生态成熟,踩坑成本低。pytest 有 pytest-html、pytest-xdist、pytest-ordering、pytest-rerunfailures 这些插件,几乎覆盖了所有日常需求。requests 库则是 Python 世界公认的 HTTP 客户端标准,没有理由不用。

如果你是在 Java 技术栈的公司,选 TestNG 或 RestAssured 也完全可以,但下面讲的分层思想和模块设计思路是通用的,换一门语言同样适用。

1.2 框架的四个核心诉求:数据、逻辑、执行、反馈分离

在动手之前,我先把自己的需求列成了四个词:分离、复用、清晰、可追踪。

  • 是什么
  • 能做什么
  • 解决什么问题
  • 适合谁来学习/参考

具体来说:

  1. 测试数据和测试逻辑分离。同一个接口,线上环境和测试环境的入参可能不一样,不同业务场景下的预期值也不一样。如果这些数据硬编码在用例代码里,每次改动都要动代码,风险高还容易改错。数据应该独立存放在 yaml、json 或 excel 文件里,代码只负责"读取 + 执行 + 断言"。
  2. 公共逻辑要可复用。发送请求、处理响应、日志记录、失败重试,这些是所有用例都会用到的能力,必须封装成独立模块,不允许在每个用例文件里各写一份。
  3. 测试结果要清晰可读。谁跑挂了、挂在哪一步、请求参数是什么、响应返回了什么,这些信息必须在报告里一目了然。不然排查问题时你会疯掉——明明报错了,却不知道当时发出去的请求长什么样。
  4. 执行链路要可追踪。每次跑批之后,要能回溯用的是哪份配置、哪个环境、哪个代码版本,这样才能保证"这次失败是代码变更引起的,还是接口本身出了问题"这个结论是可信的。

这四个诉求就是后面所有设计决策的判断标准。任何设计方案,只要违背其中一条,我都会重新考虑。

2. 工程结构设计:先把骨头立起来

不管框架后面长多大,一开始的骨架最重要。骨架歪了,后面往上堆肉一定会越堆越乱。

2.1 目录分层与模块职责

我搭框架的第一版目录结构是这样的:

api_test_framework/ ├── config/ # 配置文件存放目录 │ ├── __init__.py │ ├── dev.yaml # 开发环境配置 │ ├── test.yaml # 测试环境配置 │ └── prod.yaml # 生产环境配置 ├── core/ # 框架核心代码 │ ├── __init__.py │ ├── request_client.py # 请求封装 │ ├── assertion.py # 断言封装 │ ├── log_util.py # 日志工具 │ └── yaml_util.py # yaml 读写工具 ├── data/ # 测试数据文件 │ ├── __init__.py │ └── login_cases.yaml # 登录接口用例数据 ├── testcases/ # 测试用例代码 │ ├── __init__.py │ ├── conftest.py # fixture 定义 │ └── test_login.py # 登录接口测试 ├── reports/ # 测试报告输出目录 ├── logs/ # 日志输出目录 ├── run.py # 统一入口执行文件 └── requirements.txt # 依赖包清单

每个目录的职责是严格划分的:

  • config:只放环境信息,比如不同环境的 base_url、数据库连接信息、超时时间、全局默认请求头。不允许在代码里硬编码任何环境地址。
  • core:框架的底层能力,相当于"操作系统"层。这层代码不关心你测的是什么业务,只提供通用工具。
  • data:纯数据文件,用 yaml 居多。一个文件名对应一个大模块的测试数据。
  • testcases:业务用例层,也就是"应用程序"层。这里的代码只描述业务场景:调用哪个接口、传什么参数、期望什么结果。
  • reports 和 logs:产物目录,跑完自动生成。

这套分层最大的好处是:新人接手时,看一眼目录结构就能知道什么东西该放在哪里,不需要你费口舌解释。项目规范一旦靠目录结构本身来表达,维护成本就低了一大半。

2.2 依赖管理和环境准备

requirements.txt是我每次新建项目都先写好的文件,核心依赖就五个:

pytest==7.4.0 requests==2.31.0 PyYAML==6.0 pytest-html==4.0.2 pytest-rerunfailures==12.0

安装命令一条搞定:

pip install -r requirements.txt

有两点要提醒你:

  • 版本一定要锁死。我之前遇到过 PyYAML 从 5.x 升到 6.x 之后,部分yaml.safe_load()的加载行为有细微变化,导致配置文件解析出错。锁版本能避免这种"昨天还能跑,今天突然挂了"的灵异事件。
  • 虚拟环境是必须的。用 venv 或 conda 建独立环境,别把框架依赖和系统 Python 混在一起。开发到后期你会装各种插件,混环境等于自找麻烦。

2.3 全局配置管理:环境切换的核心

配置管理这个环节,我要单独拿出来详细讲,因为这是大多数初学者最容易忽略、后期最痛苦的问题。

我的做法是在config/目录下放多份 yaml 文件,每份对应一个环境。比如dev.yaml长这样:

env: dev base: base_url: "http://127.0.0.1:8000" timeout: 10 retry: 2 db: host: "127.0.0.1" port: 3306 user: "root" password: "123456" database: "test_db" headers: content_type: "application/json" user_agent: "AutoTest/1.0"

然后写一个配置读取的封装,放在core层。我的实现策略是:先给一个默认环境,每次执行前可以通过命令行参数或环境变量来覆盖。

这里有个非常重要的小细节:配置文件里如果能带上env这个键值,跑批之后报告里就能显示当前环境。不然测试挂了,你都不知道是哪个环境挂的,排查效率直接减半。我早期没加这个字段,吃过不少亏。

3. 核心模块实现:从写死到可复用

骨架立好了,接下来就要填"肉"了。这一章是整个框架的重头戏,我会把每个核心模块的实现思路和完整代码都放出来。

3.1 请求封装:你想要的只是"发请求、拿结果",而不是每次写一堆参数

requests 库本身已经很简洁了,但直接在用例里调用requests.get()或requests.post()仍然有三个问题:

  1. 每个用例都要写完整的 url,一旦 base_url 变了,所有用例全要改。
  2. 所有接口都要带 token,你得在每次请求里手动加上headers。
  3. 没有统一的日志记录,请求耗时、响应状态这些关键信息全丢了。

所以我封装了一个RequestClient类,统一处理这些杂事。核心逻辑如下:

# core/request_client.py import requests import time from core.log_util import logger from core.config import get_config class RequestClient: def __init__(self): cfg = get_config() self.base_url = cfg["base"]["base_url"] self.timeout = cfg["base"]["timeout"] self.default_headers = cfg.get("headers", {}) # 从 conftest 或外部传入的全局 token,动态注入 self.extra_headers = {} def set_auth_token(self, token): self.extra_headers["Authorization"] = f"Bearer {token}" def request(self, method, path, **kwargs): url = self.base_url + path headers = {**self.default_headers, **self.extra_headers} headers.update(kwargs.pop("headers", {})) start = time.time() logger.info(f"请求 {method.upper()} {url} params={kwargs.get('params')} json={kwargs.get('json')}") try: resp = requests.request( method, url, headers=headers, timeout=self.timeout, **kwargs ) except requests.Timeout: logger.error(f"请求超时: {url}") raise except requests.RequestException as e: logger.error(f"请求异常: {url}, 错误信息: {e}") raise duration = round(time.time() - start, 3) logger.info(f"响应状态码 {resp.status_code}, 耗时 {duration}s, 响应内容: {resp.text[:500]}") return resp

封装之后,用例里发请求就变成这样:

client = RequestClient() resp = client.request("get", "/api/v1/user/info")

用起来干净利落,日志自动记录,token 自动带上。注意我上面返回的是resp原始响应对象,而不是解析后的 json,这是有意为之——断言层需要拿到完整的响应状态码和响应体信息,如果你在最底层就把它解析成 json 了,再想取状态码就麻烦了。

3.2 断言封装:把"校验"做成一等公民,而不是每次打补丁

断言是接口自动化的灵魂。大多数初学者对断言的认知停留在assert resp.status_code == 200,但真实接口返回的校验远不止这么简单:

  • 业务码是 0 还是 200,不同公司定义不一样;
  • 关键字段是否存在;
  • 列表长度是否符合预期;
  • 返回结果里的某条数据是否是期望值。

我把断言封装成了一个AssertCheck类,内置几种常用的校验方式:

# core/assertion.py import json class AssertCheck: @staticmethod def assert_status_code(resp, expected_code=200): assert resp.status_code == expected_code, \ f"状态码校验失败, 期望 {expected_code}, 实际 {resp.status_code}" @staticmethod def assert_business_success(resp, success_code=0): """业务码校验, 兼容公司自定义的成功码""" body = resp.json() code = body.get("code") assert code == success_code, f"业务码校验失败, 期望 {success_code}, 实际 {code}" @staticmethod def assert_contains_key(resp, keys): """校验 JSON 响应中是否包含指定字段""" body = resp.json() missing = [k for k in keys if k not in body] assert not missing, f"响应缺少字段: {missing}, 完整响应: {json.dumps(body, ensure_ascii=False)[:500]}" @staticmethod def assert_value_equal(resp, key, expected): body = resp.json() actual = body.get(key) assert actual == expected, f"字段 {key} 校验失败, 期望 {expected}, 实际 {actual}"

这儿有一个非常关键的设计考虑:断言失败后的错误信息必须足够详细。很多新手写断言只写一句assert a == b,失败以后日志里就一行AssertionError,完全不知道怎么排查。所以我所有的断言方法都会把期望值、实际值,甚至部分响应内容拼到错误信息里。这个小习惯,能让你排查问题时节省大量时间。

3.3 日志模块:没有日志的自动化测试等于黑盒

日志模块看着不起眼,但在自动化框架里是救命稻草级别的存在。我用的log_util.py是典型的"日志配置模板":

# core/log_util.py import logging import os from datetime import datetime def setup_logger(): log_dir = "logs" os.makedirs(log_dir, exist_ok=True) log_file = os.path.join(log_dir, f"test_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log") logger = logging.getLogger("api_test") logger.setLevel(logging.DEBUG) fmt = logging.Formatter("%(asctime)s | %(levelname)s | %(filename)s:%(lineno)d | %(message)s") fh = logging.FileHandler(log_file, encoding="utf-8") fh.setFormatter(fmt) sh = logging.StreamHandler() sh.setFormatter(fmt) logger.addHandler(fh) logger.addHandler(sh) return logger logger = setup_logger()

这里要注意:日志文件最好按时间戳命名,而不是固定用test.log。因为跑一次测试就覆盖前一次的日志,遇到问题想回头查历史记录根本没机会。有了时间戳,每次跑批都会留档,排错能力和流程追溯性完全不是一个档次。

4. 数据驱动:用一套代码跑上百条用例

框架的封装做完,接下来就是最重要的工程化环节——数据驱动。没有数据驱动,你的框架就是一堆"能用但不好用"的代码;有了数据驱动,你才敢说这是个"框架"而不是"脚本集合"。

4.1 数据文件选型:yaml、json 还是 excel

先聊聊选型。我用过三种格式,最终长期用的是 yaml。选它的理由:

  • 写起来简洁。同样的数据结构,yaml 的代码量大概是 json 的 60%,少一堆花括号和引号。
  • 支持注释。这一点是 json 的死穴。在 yaml 里,我可以给每条用例备注业务场景,json 不行。
  • 层级结构清晰。接口测试数据天然是"接口-场景-参数"这种多层嵌套结构,yaml 靠缩进表达层级,一眼就能看出归属关系。

Excel 我也用过,好处是业务人员也能编辑,不需要懂代码。但问题在于:Excel 的单元格一旦被不小心改动格式,脚本直接读崩;而且多人协作时冲突处理很麻烦(需要额外引入 Git LFS 等方案)。如果不是有非技术同事参与维护的强需求,我不推荐用 Excel。

4.2 pytest 参数化的具体写法

数据驱动在 pytest 里的核心机制就是@pytest.mark.parametrize。我习惯的组合方式是:yaml 文件存储数据,代码里读取并传给参数化装饰器。

举个例子,这是data/login_cases.yaml:

login_success: - case_name: "正确用户名密码登录" payload: username: "admin" password: "123456" expect: code: 0 msg: "登录成功" - case_name: "正确用户名错误密码登录" payload: username: "admin" password: "wrong" expect: code: 1001 msg: "用户名或密码错误" - case_name: "空用户名登录" payload: username: "" password: "123456" expect: code: 1002 msg: "用户名不能为空"

然后写一个读取 yaml 的工具函数:

# core/yaml_util.py import yaml import os def load_yaml(file_path): with open(file_path, "r", encoding="utf-8") as f: return yaml.safe_load(f)

再看testcases/test_login.py的写法:

import pytest from core.request_client import RequestClient from core.assertion import AssertCheck from core.yaml_util import load_yaml cases = load_yaml("data/login_cases.yaml")["login_success"] client = RequestClient() @pytest.mark.parametrize("case", cases, ids=lambda c: c["case_name"]) def test_login(case): resp = client.request("post", "/api/v1/login", json=case["payload"]) AssertCheck.assert_status_code(resp, 200) AssertCheck.assert_business_success(resp, case["expect"]["code"]) AssertCheck.assert_value_equal(resp, "msg", case["expect"]["msg"])

跑一下看看效果:

pytest testcases/test_login.py -s --tb=short

pytest 会自动把每条用例的case_name显示在测试节点名称里,一目了然。新增用例只是往 yaml 里加一段,不用动任何代码。这就是数据驱动最直接的价值。

4.3 真正的"用例"应该长什么样

你可以对比一下数据驱动前后,同样功能的两版代码差异:

  • 数据驱动前的用例代码量:每条用例 20 行左右,100 条用例 = 2000 行代码。
  • 数据驱动后的用例代码量:核心逻辑 15 行,100 条用例 = 15 行代码 + 一个 yaml 文件。

这意味着什么?意味着用例的增删改从"程序员的工作"变成了"任何人照着格式填表就能完成的工作"。团队里如果有业务测试伙伴,他们完全可以自己维护用例数据,不需要天天来找你帮忙改代码。这是数据驱动真正解放生产力的地方。

5. 依赖与隔离:遇到接口之间有关联怎么办

现实世界的接口测试永远不可能是"每个接口孤零零地测",最常见的场景就是:先登录拿 token,再带着 token 访问业务接口;或者创建订单之后,才能对订单进行后续操作。这个环节处理不好,框架就会退化成一堆互相纠缠的"面条代码"。

5.1 token 管理和认证机制的处理

token 管理是我用了多种方案之后才沉淀出的一套思路。先说方案,再说为什么。

第一种方案是在每个用例文件里自己setup里调登录接口拿 token。这是最直觉的写法,但问题也很明显:token 拿一次要调一次登录,接口多的时候,跑一遍全量用例要产生几十次无效的登录请求,污染接口统计不说,还拖慢执行速度。

第二种方案是把 token 放进 conftest.py 里做成 session 级别的 fixture,整个测试生命周期只执行一次登录,token 自动注入所有用例。这是我现在用的方案:

# testcases/conftest.py import pytest from core.request_client import RequestClient from core.yaml_util import load_yaml login_data = load_yaml("data/login_cases.yaml")["login_success"][0] @pytest.fixture(scope="session", autouse=True) def global_token(): """整个测试会话只执行一次登录,token 注入请求客户端""" client = RequestClient() resp = client.request("post", "/api/v1/login", json=login_data["payload"]) assert resp.status_code == 200, f"登录失败: {resp.text}" token = resp.json().get("data", {}).get("token") assert token, f"登录响应中未获取到 token: {resp.text}" client.set_auth_token(token) return token

使用逻辑很简单:

  • scope="session"保证整个 pytest 进程只登录一次。
  • autouse=True表示所有测试用例不用显式指定依赖,自动生效。
  • 登录数据也从data目录读取,不硬编码在 conftest 里。

这个方案跑全量用例时,登录只产生一次请求,token 全局复用,且强制保证了所有用例在带鉴权的条件下执行。实测下来,全框架跑 300 多条用例,认证相关请求从 300 多次降到了 1 次,效率提升非常显著。

5.2 用例依赖处理的思路:慎用 pytest-ordering

还有一个比 token 更棘手的问题:某些用例强依赖前序用例的执行结果,比如"创建订单后必须用订单号去查询"。

我的建议是:优先通过接口调用、而不是测试顺序来管理依赖。具体说就是,在用例里通过前置接口动态获取依赖数据,而不是靠@pytest.mark.run(order=1)这种方式强行规定先后顺序。

原因有两点:

  • pytest-ordering这种插件在pytest-xdist并发执行模式下完全不兼容。你想并发加速跑批,一加上 conftest 里的顺序依赖就崩。
  • 顺序依赖的隐藏问题是:一旦前面的用例失败,后面所有依赖它的用例都会连环失败,你根本分不清是业务问题还是测试数据问题。动态获取依赖数据则完全消除了这种耦合。

那什么时候才用pytest.mark.run(order=1)呢?我只有在"同一个接口的多条用例必须保证执行顺序"这种极小范围内才用,而且会尽量避免依赖失败用例的结果。

5.3 测试数据清理:别忘了给环境"擦屁股"

这个话题很多教程完全不会提,但在真实项目里几乎天天遇到。接口测试不管在哪个环境跑,多多少少都会产生脏数据,尤其是写操作类的接口。

我的做法是在 conftest.py 里注册一个 autouse 的 fixture,结合 pytest 的yield机制实现"前置准备 + 后置清理":

@pytest.fixture(autouse=True) def clean_data(): # 前置:准备测试数据需要的独立 namespace yield # 后置:回收本用例产生的数据 client = RequestClient() client.request("delete", "/api/v1/testdata/clean", params={"timestamp": 123456})

不过这里要控制好一个度:不是每条用例都需要清理,查询类和只读类接口的清理往往是浪费时间的。我一般只对增删改类接口做清理,而判定方式是看接口响应有没有 code 层面的副作用。

6. 报告、CI 集成和那些绕不开的坑

框架能跑不是终点,能"给别人看"、"能稳定地自动化跑"才是工程化的关键。这一章把压轴部分讲完。

6.1 报告插件选型:pytest-html 和 Allure 怎么选

报告是整个框架的"脸面",也是验收时最容易被关注的部分。我两种方案都深度用过,给你做个对比:

对比项pytest-htmlAllure
安装成本极低, pip 直接装需要额外安装 allure 命令行工具
报告观感简洁,中规中矩美观,支持趋势图、分类统计
历史数据单次报告,可叠加合并自带历史结果存储,支持图形化趋势
与 pytest 集成插件级,零侵入断言失败时截图等增强能力更强
适合场景小型团队、快速落地中大型团队、需要多维度数据沉淀

pytest-html 的接入方法:

pytest testcases/ --html=reports/report.html --self-contained-html

Allure 的接入方法:

pytest testcases/ --alluredir=reports/allure-results allure generate reports/allure-results -o reports/allure-report --clean

我个人现在的选择是 Allure。虽然要多装一个命令行工具,但它的用例分组、执行历史、失败率趋势这些能力确实好用。做质量度量的同学,直接就能从 Allure 的界面截图里拿到数据。

6.2 CI 集成:让框架自动跑起来

框架只有集成到 CI 里才算真正"上线"。我司用的 Jenkins,配置思路给你列一下:

  1. 在 Jenkins 里新建一个自由风格项目。
  2. 源码管理里配置 Git 仓库地址,每次 push 到特定分支触发。
  3. 构建步骤选"Execute Shell":
cd api_test_framework pip install -r requirements.txt python run.py
  1. 构建后操作里,把reports/目录下的 html 报告路径配置到 "Publish HTML reports" 插件,这样每次构建完,团队成员直接浏览器打开就能看。

run.py是我的统一入口,核心代码很简洁:

import pytest import sys from datetime import datetime if __name__ == "__main__": report_name = f"reports/report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html" exit_code = pytest.main([ "testcases/", f"--html={report_name}", "--self-contained-html", "--maxfail=5", "-q", ]) sys.exit(exit_code)

这里有个细节:--maxfail=5表示失败 5 条用例就停止执行。这样做的目的是节省全量跑批的时间——如果前 5 条就挂光了,大概率是环境问题或框架问题,没必要继续跑完全部用例浪费时间。当然,日常调试时要改成--maxfail=0跑全量,看完整结果。

6.3 我实际踩过的几个坑

这部分是我最想分享的内容——都是真金白银换来的教训。

坑一:yaml 文件里的数字被转为 int 导致断言失败

有阵子登录接口返回的code字段在 JSON 里是字符串"0",但我在 yaml 写的是code: 0,PyYAML 给读成了 int。业务侧代码是if code != 0判断的,字符串"0"其实是被隐式转换的,但接口返回来的是字符串,断言写== 0就挂了。解决方式是:要么在断言层做类型转换,要么在 yaml 里给code加引号写成"0"。这个坑很隐蔽,排查了很久才发现是类型不一致。

坑二:请求超时时间设太短,测试环境一慢就误报

最初我把 timeout 设成 3 秒,本地联调没问题,一放到测试环境就疯狂超时。后来发现是测试环境服务端有定时任务,高峰时段响应能拖到十几秒。最后我在配置里把 timeout 拆成了"连接超时 + 读取超时"两个参数,并单独对慢接口打了标签超时放大。框架的默认建议值是连接 5 秒、读取 30 秒,具体看你们接口性能而定。

坑三:跑批之前忘记检查测试环境是否就绪

这个问题听起来很蠢,但是发生的频率超乎想象——辛辛苦苦跑完 300 条用例,发现环境压根没启动,全部ConnectionError。后来我在 conftest.py 里加了一个 session 级别的 fixture,执行任何用例之前先 ping 一下配置里的 base_url:

@pytest.fixture(scope="session", autouse=True) def env_ready_check(): import requests cfg = get_config() base_url = cfg["base"]["base_url"] try: requests.get(base_url, timeout=3) except requests.RequestException: raise RuntimeError(f"环境未就绪, 无法访问 {base_url}, 请先检查被测环境状态")

跑批前 1 秒就能发现问题,以前是 30 分钟全挂完才发现。这个 fixture 是我觉得性价比最高的一个改进。

6.4 后续还能往哪些方向扩展

框架搭到这一步,其实已经能支撑大部分接口测试需求了。如果你还想要更强的能力,我建议按以下优先级逐步扩展:

  1. 接口之间的数据链路打通:把 SendRequest 和 SQL 查询封装成一对组合,实现"操作接口 -> 查库校验数据落库"的闭环断言。
  2. 性能测试插桩:在request_client里埋点记录每次请求耗时,聚合后输出到报告,每个月跑一次轻量级回归,能提前发现性能劣化。
  3. Mock 服务集成:配合 pytest-httpx 等库,对依赖的第三方接口做 Mock,让框架可以脱离外部系统单独跑。
  4. 多协议支持:现在很多业务接口已经从 HTTP 扩展到 gRPC 或 WebSocket,可以在core层抽象出统一的协议适配器接口,将来新加协议时不用动用例层代码。

我个人在实际操作中的体会是:搭建框架这件事,技术难度其实不算高,难点在于克制——克制自己"什么功能都想加"的冲动,克制"一版就要做成完美平台"的野心。第一版框架只需要满足真实业务场景的 80% 需求就够了,剩下的 20% 留给后续迭代。跑通、稳定、团队用起来顺手,比"功能大全但没人愿意用"重要得多。

最后再分享一个小技巧:每当你准备给框架加一个新功能之前,先用一句话写下这个功能解决了什么问题。如果这句话说不清楚,这功能就别加。保持框架的"小而美",你的维护成本会低到不可思议。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询