接口自动化测试框架,几乎每个测试团队都绕不过去。你从简单的 requests 脚本开始跑通第一个用例,再慢慢加个读 Excel 的函数,再来个发邮件的模块,到最后这堆脚本到底该怎么组织,就成了绕不开的问题。我在好几个项目里都经历过这个阶段,所以这篇东西想跟你聊聊,怎么从零把一个 API 自动化测试框架认认真真地设计出来、搭起来、跑下去,而不是等用例攒到几百条之后才开始返工。
先说清楚这篇文章解决什么问题:它不是教你怎么写单个测试用例,而是帮你搭好一套骨架——用 Python + pytest 构建一个分层清晰、数据驱动、可多人协作、能接 CI 的接口自动化框架。适合刚接触自动化测试的测试工程师、想重构现有脚本的测试开发,以及需要在团队里推动 API 自动化的技术负责人。
1. 从零开始:API自动化测试框架的整体设计思路
1.1 先问自己:你是需要脚本,还是需要框架
很多人的第一反应是:用 Postman 跑一遍,或者写个 Python 脚本循环调用就完事了。但"能跑"和"能被长期维护"完全是两回事。我判断一个团队是否真的需要专门搭框架,通常看四个信号:
- 有没有多环境需求。测试环境、预发环境、生产环境来回切换,改一个 baseURL 就得全局搜索替换,很快会出事故。
- 用例数量是不是在持续增长。脚本写到一百条以上,如果没有统一封装和规范,维护成本会指数级上升。
- 是不是需要多人协作。小脚本可以一个人闷头写,但团队协作时,每个人命名风格、断言写法都不一样,合并代码就是一场灾难。
- 要不要接入 CI/CD。流水线里跑自动化测试,必须能安静地执行、稳定地出报告,还需要有清晰的失败定位信息。
如果上面四条一条都不占,项目也就三五个接口,那确实没必要上重框架,Python 脚本加 requests 够用。但只要你开始考虑"自动化测试"这件事本身,我的建议是直接按照框架的思路去设计。这就像工具箱和生产线的关系:工具能帮你干活,但生产线的价值在于把每个环节固定下来,让不同的工人做同一件事时产出的结果一致。
1.2 技术栈选型:Python+pytest 还是 Java+RestAssured
框架选型是团队里最容易吵架的话题,但吵来吵去,核心考量就三个:团队技术栈、生态成熟度、上手成本。
| 对比维度 | Python + pytest + requests | Java + RestAssured + TestNG/Maven |
|---|---|---|
| 上手门槛 | 低,脚本基础即可 | 中高,需要 Java 和构建工具基础 |
| 用例组织 | pytest 的 fixture 和 parametrize 非常灵活 | TestNG 注解丰富,适合工程化团队 |
| 数据驱动 | YAML/JSON/Excel 加载方便 | 需要额外封装 POI 或读取工具 |
| 报告生态 | Allure、pytest-html 都很成熟 | Allure、ExtentReports 同样成熟 |
| 与研发代码集成 | 弱,通常独立项目运行 | 强,可以放进 Maven 工程跟主项目一起构建 |
| 典型场景 | 独立测试工程、快速落地 | 企业内部平台、强耦合研发流水线 |
就我个人的偏好来说,如果团队没有强 Java 背景,我会坚定不移地选 Python + pytest。原因很简单:接口自动化的核心工作量在于"组织用例"和"维护数据",Python 在这两件事上的效率实在太高了。pytest 的 fixture 机制能解决大量 setup/teardown 的样板代码,parametrize 配合 YAML 数据文件几乎就是为数据驱动量身定做的。
但这不意味着 Java 路线不行。如果你的团队本身就是 Java 技术栈,测试同学都熟悉 Maven 和 Spring Boot,那 RestAssured + TestNG 放进 Maven 工程里构建,跟研发的代码共用 JVM 环境,配合 CI 里的 Maven 构建流程确实更顺滑。网上搜"java接口自动化测试框架"能看到很多现成方案,基本模式都一样,只是语言不同。
1.3 分层架构:让骨架稳定、让肉长对地方
框架搭建最容易犯的错误,就是所有代码都堆在 testcase 目录里,业务逻辑和请求细节混在一起。我推荐的分层方式是这样:
- 配置层:管理环境地址、账号、超时时间、开关项。配置集中,环境切换就是改一个配置文件的事。
- 核心层:封装 requests、处理 session、鉴权、日志、重试、统一的请求出口。测试代码不应该直接 import requests,而应该走自己封装的 client。
- 数据层:存放测试数据文件,包括用例数据、预期结果、用户账号、造数脚本。数据与代码分离。
- 业务层(可选):把业务操作封装成方法,比如"创建订单""支付订单",这种能被多个用例复用的步骤抽出来。
- 用例层:只做三件事——读取数据、调核心层、断言结果。不关心请求怎么发出去的,也不关心报告怎么生成。
- 报告层:收集执行结果,输出可读的测试报告,并考虑失败信息的可追溯性。
为什么要强调"单向依赖"?因为每个模块都应该只依赖它下面的层,不反向依赖。配置层谁也不依赖,核心层依赖配置层,用例层依赖核心层和数据层。一旦出现用例层里直接改配置、核心层里拼业务逻辑的情况,框架就开始腐烂了。用一句大白话总结:配置归配置,请求归请求,数据归数据,用例只负责表达业务场景。
2. 框架核心细节解析:设计对了,维护成本才降得下来
2.1 模块划分:目录结构里藏着的设计哲学
一个建议的初始项目结构是这样:
api_test_framework/ ├── config/ │ ├── config.yaml # 环境配置 │ └── settings.py # 全局参数和路径管理 ├── core/ │ ├── http_client.py # requests 封装 │ ├── auth.py # 鉴权处理 │ └── logger.py # 日志模块 ├── data/ │ ├── test_cases/ │ │ ├── order_api.yaml │ │ └── user_api.yaml │ └── users.yaml # 测试账号数据 ├── testcases/ │ ├── conftest.py # pytest 全局 fixture │ ├── test_order_api.py │ └── test_user_api.py ├── common/ │ ├── assertions.py # 断言封装 │ └── utils.py # 加解密、时间戳等工具 ├── reports/ # 测试报告输出 ├── requirements.txt └── pytest.ini这个结构没有放额外的构建脚本,因为 pytest 本身就是用例执行器。你可能会问:为什么不把 config 和 data 合并?我的经验是,配置和数据虽然都是"非代码",但生命周期完全不同。配置跟着环境走,测试环境、预发环境各有一套;数据跟着业务走,一套数据可能在多个环境里复用。混在一起,一旦环境切换,用例数据也跟着错乱,排查起来特别痛苦。
pytest.ini 也是一开始就该建好的文件。它不只是配置项,更是在告诉整个项目"根目录在哪里"。我在实际项目中见过太多次因为缺少这个文件,导致 conftest.py 不生效、用例 imports 全部报错的惨剧。
[pytest] testpaths = testcases addopts = -v -s --maxfail=102.2 数据驱动设计:用例和数据要分开
数据驱动的核心诉求是一个不需要写代码的人也能添加用例。业务同学或者新入行的测试,不需要理解 Python,只需要照着 YAML 文件的格式往里面追加一条数据,用例就能跑起来。这就是数据与代码分离最大的价值。
三种主流数据格式的选型,我这里直接给结论:
- YAML:最适合做用例数据。层级清晰、支持注释、写起来不啰嗦,而且 PyYAML 加载后自动转成 dict/list,跟 Python 无缝衔接。
- JSON:程序生成数据时友好,但手工编辑体验差,不能写注释,层级一深就眼花。
- Excel:适合不懂代码的业务同学维护,但读取性能和格式校验都是坑,还容易因为单元格格式问题产生莫名其妙的 bug。
一个标准的 YAML 用例数据文件长这样:
# data/test_cases/order_api.yaml cases: - name: "正常创建订单" method: POST url: /api/v1/order/create headers: Content-Type: application/json params: {} json: user_id: 10001 product_id: "PRD202401" quantity: 2 expected: status_code: 200 business_code: 0 check_fields: order_no: "not_empty" - name: "参数缺失时返回错误" method: POST url: /api/v1/order/create headers: Content-Type: application/json json: user_id: 10001 expected: status_code: 200 business_code: 40001 check_fields: msg: "product_id is required"注意到我特意把 expected 拆成了 status_code、business_code、check_fields 三层,这背后是有讲究的。HTTP 状态码 200 不代表业务成功,很多系统的业务错误也是返回 200,靠响应体里的 code 字段区分。所以断言设计一定要区分"传输层状态"和"业务层状态",这个细节我们下一节展开讲。
2.3 断言设计:状态码只是第一层
断言是自动化测试里最容易写"爽"但也最容易写"废"的地方。新手拿到接口,看到 JSON 就整个并进去比较,结果字段一多、动态值一变,用例永远红。断言要分层,而且要克制:
- 第一层:HTTP 状态码。这是传输层校验,确认请求没有 404、500、401 这些传输异常。但仅此而已,不要指望它验证业务。
- 第二层:业务码。响应体里通常有 code/status/businessCode 之类的字段,它才是业务成功与否的真相。
- 第三层:关键字段。用 jmespath、jsonpath 或者直接递归取值的方式,校验真正影响业务的字段,比如订单号非空、返回列表长度符合预期。
- 第四层:跨系统校验。如果允许,校验数据库落库结果、调用链日志、或者依赖的消息队列数据。这一层成本高,适合放在核心主流程用例里,不适合全量铺开。
我见过太多把整个响应体快照拉出来做 == 断言的用例。一次小小的前端文案改动,就能让十几个用例同时挂掉,而真正要验证的核心业务逻辑根本没被覆盖到。断言做得"少而准",维护成本低,还能在失败时快速定位问题。反例是另一个极端——只断言 status_code == 200,这种用例跑完一片绿,但业务全挂了你都不知道。
2.4 鉴权处理:token、API Key 与动态签名
接口自动化的鉴权处理,是新人最容易卡住的地方。最常见的业务系统是登录后拿 token,后续请求在 header 里带 Authorization。框架层面的处理思路应该是:把鉴权当成一次 fixture,而不是在每个用例里手动写。
# testcases/conftest.py import pytest from core.auth import AuthManager from config.settings import Settings @pytest.fixture(scope="session") def auth_token(): """整个测试会话只登录一次,后面的用例复用 token""" settings = Settings() token = AuthManager.login(settings.env, settings.admin_account) if not token: pytest.fail("登录失败,请检查账号配置") return token @pytest.fixture() def client(auth_token): """每个用例拿到一个绑定好鉴权的请求客户端""" from core.http_client import ApiClient return ApiClient(token=auth_token)用 session 级的 fixture 做登录,意味着整个测试执行周期只调用一次登录接口,几十个用例共享同一个 token,执行速度不会因为反复登录而拖慢。当然这里要注意 token 有效期的问题,如果被测系统的 token 有效期短于整个测试执行周期,就得在客户端封装里加一个"token 失效后自动重新登录"的机制,这个细节放到第 4 章讲。
另外一类接口不走登录 token,而是用 API Key。现在很多第三方开放平台、大模型 API 都是这种模式,直接把 key 塞在 header 或者请求参数里。这类鉴权的坑集中在管理上:key 分散在每个人的本地配置里、提交代码时把真实 key 带进了仓库、key 过期后没有统一刷新机制。我在第 4 章会专门聊 401 的排查思路,这里先记住一个原则:所有密钥类信息必须走配置或环境变量,禁止硬编码在用例代码里。
3. 从零构建:一个可落地的API自动化测试框架实操
3.1 环境准备与项目骨架搭建
开始动手前,先把 Python 环境和依赖确定下来。我建议一开始就用虚拟环境,避免不同项目之间的包互相污染。requirements.txt 控制在最少依赖,下面这几样基本就能覆盖一个标准接口自动化项目:
requests==2.31.0 pytest==8.1.1 pytest-html==4.1.0 PyYAML==6.0.1 jmespath==1.0.1 allure-pytest==2.13.5这里的版本号是我实测相对稳定的组合,写死版本是避免"昨天还能跑今天突然挂了"的经典悲剧。pytest 8.x 跟 pytest-html 4.x 的配合在生成报告时比较顺畅,allure 则用于更华丽的报告展示。注意不要贪多:像 selenium 这种 UI 自动化依赖,跟接口自动化项目混在一起会让环境变得臃肿,也没必要。
项目骨架按照第 2 章的目录结构建好之后,要把 pytest.ini 和 conftest.py 放在正确的位置。conftest.py 是 pytest 最强大的钩子之一,放在 testcases 目录下,它就能给该目录下所有测试文件共享 fixture,不需要 imports,不需要写复杂的 base class。这一步是新手最容易忽略的:fixture 明明写了,运行却不生效,很可能就是因为 conftest.py 没有被 pytest 正确收集到。
3.2 配置管理:多环境切换不再靠改代码
配置文件用 YAML 承接最直观:
# config/config.yaml env: test base_url: test: "https://api-test.example.com" staging: "https://api-staging.example.com" prod: "https://api.example.com" timeout: 10 admin_account: test: username: "admin_test" password: "xxx" prod: username: "admin_prod" password: "yyy"然后写一个 settings.py 去读取它:
# config/settings.py import os import yaml BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) class Settings: def __init__(self): with open(os.path.join(BASE_DIR, "config", "config.yaml"), "r", encoding="utf-8") as f: self._config = yaml.safe_load(f) self.env = os.getenv("RUN_ENV", self._config.get("env", "test")) @property def base_url(self) -> str: return self._config["base_url"][self.env] @property def timeout(self) -> int: return self._config["timeout"] @property def admin_account(self) -> dict: return self._config["admin_account"][self.env]这里的核心设计是支持RUN_ENV环境变量覆盖默认值。这样在 CI 里跑不同环境的用例,只需要在流水线里设置不同的环境变量,完全不用动代码。至于密码等敏感信息,真实项目中建议从环境变量、密钥管理系统读取,避免把真实凭据提交进 Git 仓库。配置文件里留的是占位符,CI 执行时注入真实值。
3.3 requests 核心封装:session、超时、重试与日志
requests 库本身已经非常好用,但在框架里还是建议包一层。因为你要解决的问题不只是发请求,还包括:统一管理超时、失败自动重试、记录请求和响应日志、统一处理错误。直接在用例里散装 requests,这些能力每个用例都得写一遍,最后必然走样。
# core/http_client.py import logging import time import requests from urllib3.util.retry import Retry from requests.adapters import HTTPAdapter logger = logging.getLogger("api_test") class ApiClient: def __init__(self, token=None, base_url=None, timeout=10): self.session = requests.Session() self.base_url = base_url self.timeout = timeout if token: self.session.headers.update({"Authorization": f"Bearer {token}"}) # 配置重试策略:连接失败或 5xx 时重试 3 次,指数退避 retries = Retry(total=3, backoff_factor=0.5, status_forcelist=[500, 502, 503, 504]) adapter = HTTPAdapter(max_retries=retries) self.session.mount("http://", adapter) self.session.mount("https://", adapter) def request(self, method, path, **kwargs): kwargs.setdefault("timeout", self.timeout) base = self.base_url.rstrip("/") url = f"{base}/{path.lstrip('/')}" start = time.time() resp = self.session.request(method, url, **kwargs) cost = round((time.time() - start) * 1000, 2) logger.info("REQUEST %s %s cost=%sms", method, url, cost) logger.info("RESPONSE status=%s body=%s", resp.status_code, resp.text[:500]) return resp这段封装有几个值得注意的点。第一,Retry 重试只对特定异常生效,total=3配合backoff_factor=0.5,第一次重试等 0.5 秒、第二次等 1 秒、第三次等 2 秒,避免对被测服务造成重试风暴。第二,timeout 必须写死默认值,因为 requests 的默认 timeout 是永不超时,一旦接口卡住,整个测试会被挂死。第三,日志里同时打印请求耗时和响应体前 500 个字符,失败排障时这就是第一手线索。
3.4 测试用例编写与数据驱动落地
有了 client 和数据文件,用例层就可以写得很干净了。核心是利用 pytest 的 parametrize 把 YAML 数据"喂"给测试函数:
# testcases/test_order_api.py import pytest import yaml import os import jmespath from common.assertions import assert_status_code, assert_business_code BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ORDER_CASES = yaml.safe_load( open(os.path.join(BASE_DIR, "data", "test_cases", "order_api.yaml"), encoding="utf-8") )["cases"] def load_order_cases(): """从 YAML 加载用例,生成 id 方便定位失败项""" for case in ORDER_CASES: yield pytest.param(case, id=case["name"]) @pytest.mark.parametrize("case", load_order_cases()) def test_order_api(case, client): resp = client.request( case["method"], case["url"], params=case.get("params"), json=case.get("json"), headers=case.get("headers"), ) assert_status_code(resp, case["expected"]["status_code"]) assert_business_code(resp, case["expected"]["business_code"]) for field, expect in case["expected"].get("check_fields", {}).items(): actual = jmespath.search(field, resp.json()) assert actual is not None, f"字段 {field} 不存在" if expect == "not_empty": assert actual, f"字段 {field} 不应为空" else: assert actual == expect, f"字段 {field} 期望 {expect},实际 {actual}"这里的 parametrize 技巧在于用id=case["name"],跑失败时日志里直接显示"正常创建订单"而不是 test_order_api[0],排障体验天差地别。从 YAML 里读expected的check_fields后,用 jmespath 做字段解析,比手动写一串resp["data"]["xxx"]要灵活得多,万一接口返回结构调整了,只需要改数据文件里的 jmespath 表达式,不用动代码。
service层的 fixture client 在每个用例里都会重新实例化,但它的 session 级 token 是共享的,所以性能上没有瓶颈。运行命令行也很直接:
# 在项目根目录执行 pytest --html=reports/report.html --self-contained-htmlpytest-html 的--self-contained-html参数建议加上,这样生成出来的单个 HTML 文件里内嵌了 CSS,跨机器分享、发到群里都不会乱版。
3.5 测试报告与 CI/CD 集成
接口自动化框架只有接到流水线里,价值才能最大化。本地跑是给开发自测用的,CI 里跑是给回归兜底用的。报告这层我一般两套并用:日常快速反馈用 pytest-html,给管理层和跨团队展示用 Allure。
Allure 的接入方式很简单,测试代码里加 allure 的标记:
import allure @allure.feature("订单模块") @allure.story("创建订单") @pytest.mark.parametrize("case", load_order_cases()) def test_order_api(case, client): ...命令行执行时换成pytest --alluredir=reports/allure-results,最后用allure generate生成静态站点报告。Allure 报告的优势是分类清晰:按功能模块、用例等级、失败原因聚合展示,特别适合用例规模上来之后的维护和汇报。
CI 集成部分,以 Jenkins 为例,流水线的关键步骤大概是:拉代码、创建虚拟环境、安装依赖、执行 pytest、归档报告、设定失败阈值。执行环境我强烈建议用 Docker 来做,把 Python 版本和依赖锁进镜像里,避免"本地能跑 CI 挂"这种经典问题。这也正好呼应项目里经常提到的"构建方式"问题:不要指望每台机器都提前装好环境,把环境本身也当成代码来管理。
4. 实战中的坑与排查实录:这些问题我几乎都踩过
4.1 401 Unauthorized:API Key 失效的常见原因
401 是接口自动化里出现频率最高的错误之一,典型报错长这样:unexpected status 401 unauthorized: incorrect api key provided。每次看到这个报错,先别急着怀疑被测服务,按下面的顺序排查:
- Key 是不是配错了环境:测试环境、预发环境、生产环境的 key 是独立的,拿错环境的 key 去请求必然 401。检查配置文件的 env 字段和当前实际运行环境是否一致。
- Key 是不是过期了:很多平台的 API Key 有有效期,尤其是团队里共用的 key,可能是某个人手动撤销过。去平台后台看 key 的状态。
- Header 格式对不对:有的接口要求
Authorization: Bearer <key>,有的要求X-Api-Key: <key>,还有的要求放在 query 参数里。别想当然,看接口文档。 - Key 前后面有没有隐藏字符:复制粘贴时带上了空格或换行符,这类问题最难发现,建议在日志里打印 key 的前几位和后几位做比对。
- 服务端时钟或签名校验:部分平台要求请求带时间戳和签名,时间偏差超过阈值就拒绝。
有一次我在项目里排查了一个小时的 401,最后发现是同事把 key 从 Excel 复制出来时,单元格的换行符被一起带进了配置。所以后面我在核心封装里加了 key 的 strip 处理,并且在加载密钥时校验 key 的格式是否符合预期前缀。
4.2 用例间数据依赖:先登录还是先造数据
接口用例之间最难搞的就是数据依赖。订单接口依赖用户登录,支付接口依赖订单创建成功,如果每个用例都独立跑,前置数据从哪来?我的建议是遵循两个原则:
- 登录态用 fixture 解决,session 级共享,但不要跨模块硬依赖。
- 前置业务数据用"接口造数"而不是"数据库造数"。比如测试支付,就在 fixture 里调用创建订单接口先拿到一个 order_no,再传给支付用例。这样造数链路跟真实业务一致,也不会因为绕过业务逻辑而产生脏数据。
数据库造数不是不能用,但只适合一些基础数据准备,比如清空某个表、插入基础配置。凡是走核心业务链路的,都尽量通过接口去造。否则你测的是接口,数据却是手插的,两者对不上时你会陷入"到底是接口的问题还是数据的问题"的泥潭。
4.3 响应超时与性能瓶颈定位
请求超时是另一个高频问题。requests 的 timeout 要分 connect timeout 和 read timeout 两层来理解:connect 指建立 TCP 连接的时间,read 指服务器返回首字节的时间。有些接口本身逻辑重,查询时间长,如果全局 timeout 设成 3 秒,那大概率天天误报。
我的做法是把默认超时设置在一个合理区间(比如 10 秒),然后针对个别慢接口在数据文件里单独覆盖 timeout 字段。核心封装里给 kwargs 设好默认值,用例数据里可以用timeout: 30覆盖,灵活度就有了。
如果超时发生在重试之后仍然失败,就要考虑被测服务是不是真的有问题了。这时候去看日志里的耗时分布,把这段时间所有用例的耗时拉出来对比,基本能判断是某个接口整体变慢,还是网络链路抖动导致的偶发超时。
4.4 执行效率与并发:几百个用例怎么跑得快
用例规模上来之后,串行执行会越来越难受。pytest 官方生态里有 pytest-xdist,可以很方便地并行执行:
pytest -n 4-n 4表示开 4 个 worker 并行跑。但并行不是免费的午餐,要注意几个问题:
- session 级 fixture 的线程安全。如果登录后 token 被多个 worker 共享,并发请求可能导致 token 失效或触发服务端的风控。
- 数据冲突。多个 worker 同时创建相同数据,可能撞唯一索引。
- 报告聚合。每个 worker 产生自己的报告,需要用 Allure 这类支持聚合的报告框架。
我的建议是:先保证用例之间无副作用,再开并发。把用例设计成"幂等优先",创建类操作尽量用唯一前缀做数据标识(时间戳加随机数),查询和更新类操作天然可并行。等用例和数据都干净了,并行带来的收益才真正体现出来。
4.5 第三方与大模型 API 的特殊问题
最近几年接口自动化的场景有一个明显的变化:被测对象从内部业务系统,扩展到了大量的第三方平台和大模型 API。这块的坑跟传统接口不太一样,说几个我实测中遇到的:
- 限流(Rate Limit):很多 AI 平台 API 对 QPM/QPD 有硬性限制,并发测试一打就容易报 429。这类接口的自动化要主动控制频率,在封装层做每分钟请求数限制,而不是盲目重试。
- 上下文长度限制:大模型 API 会报类似
400 this model's maximum context length is ... tokens的错误。这不是代码 bug,而是请求的 prompt + 输出超出了模型窗口。自动化测试这类接口时,测试数据本身要控制长度,不能把整本小说塞进去。 - 流式响应的断言:大模型接口默认可能是 SSE 流式返回,如果客户端没有
stream=True,请求会一直挂着直到超时。断言只能对完整返回结果做,不能对中间片段做。 - 密钥路由配置:多模型网关架构里,经常遇到
no api key for provider route这类的报错。这是网关配置里没有为某个模型路由绑定密钥,属于环境配置问题,不要在用例里想办法。
测试这类接口时,我额外加了两条团队规范:一是密钥和路由配置必须跟业务线隔离,二是大模型接口的用例要用真实请求加少量 mock 结合的方式跑,避免每次 CI 都消耗大量 token 费用。把接口自动化框架跟成本工具打通,这也是现在测试开发比较新的命题。
最后再分享一个我的体会
框架搭到现在,我最深的一个感触是:接口自动化测试框架不是一次性建完交付的东西,它会随着被测系统的演进而持续长肉。你不需要一开始就把它设计得尽善尽美,但要保证每一层之间的边界是清晰的,这样后面的修改才不会伤筋动骨。我自己踩过最大的坑,就是早期为了"省事"把读 YAML 的逻辑直接写进了测试用例,结果后来换了一种数据格式,几乎重写了所有用例。从那以后我再也不敢偷这个懒。
你可以从最简单的三层结构开始:配置、请求封装、用例。跑通一条主链路之后,再逐步加数据驱动、加报告、加 CI。每加一层,都问自己一句:这个改动会让别人写新用例更简单,还是更复杂?如果答案是更复杂,那就先不上。框架最大的价值不是功能多,而是让团队里每个人写出的用例都长一个样——看得懂、跑得稳、改得起。