Python + pytest 接口自动化测试框架实战:从零搭建企业级项目骨架

前言

很多接口自动化项目,最开始都长得很像:

  • 一个 pytest 测试文件

  • 几个 requests 请求

  • 几个硬编码 URL

  • 再加上一些临时拼出来的断言

一开始能跑,但到了真实项目里,问题很快就会暴露出来:

  1. 接口数量一多,重复代码迅速膨胀

  2. 登录态分散在每条用例里,维护很痛苦

  3. 环境切换、租户切换、账号切换全都容易出错

  4. 用例虽然能执行,但别人接手时根本看不懂

  5. 一旦接口路径或参数结构变动,修复成本很高

所以,接口自动化真正需要的不是"把请求发出去",而是搭一套能长期维护的测试工程

这篇文章我就结合当前这个项目,讲清楚它为什么要这样分层,以及每一层在解决什么问题。

一、这个项目适合用什么思路来理解

这个仓库不是单纯的"接口集合",它更像一套面向企业内部系统的测试基础设施。

从目录上看,核心能力分布在几个层次:

  • client/:处理 HTTP 请求

  • common/:配置、鉴权、断言、日志、数据库、变量存储

  • services/:业务封装层

  • fixtures/:pytest 注入层

  • config/:环境配置和接口路径定义

  • testcases/:只写业务场景的测试层

这种结构最重要的价值不是"看起来整齐",而是让每一层只做自己该做的事。

举个最直观的例子:

如果某个接口的路径改了,理想情况下你只需要改 config/api_path/*.yaml。 如果某个系统的 token 生成逻辑变了,应该只改 common/auth_manager.py。 如果某个业务接口参数变了,优先改 services/,而不是一口气去改几十条 testcases/

这就是分层的意义。

二、为什么接口自动化不能只靠 requests + pytest

很多人第一次做接口自动化,都会有一种直觉:反正接口测试就是发 HTTP 请求嘛,那我直接写脚本就行了。

这个想法在最初是成立的,但只适合下面这种场景:

  • 接口少

  • 测试目的简单

  • 没有复杂鉴权

  • 不需要多人协作

  • 不需要长期维护

而企业项目通常不是这样。真实环境里会有这些问题:

1)接口是变动的

路径、参数、header、响应结构都可能调整。

如果请求逻辑和测试逻辑绑死在一起,任何变化都会让用例大面积重写。

2)登录是复杂的

你的项目里就不是简单拿一个 token,而是涉及:

  • SSO 登录

  • code 交换

  • 不同业务系统的token 获取

  • 不同角色账号切换

  • 不同业务系统头部信息注入

3)测试不只是"成功就行"

接口自动化真正关心的是:

  • 返回值对不对

  • 业务数据是否正确

  • 数据库是否落库

  • 不同角色是否看到不同数据

  • 场景链路是否完整

所以,脚本化写法很快就会到天花板。要想长期做下去,就必须工程化。

三、技术栈选型背后的原因

项目里核心依赖并不复杂:

  • pytest:测试组织与执行

  • requests:HTTP 请求

  • PyYAML:配置和接口定义

  • allure-pytest:报告展示

  • jsonpath-ng:字段提取

  • deepdiff / jsonschema:结构和内容断言

  • pymysql:数据库校验

  • python-dotenv:环境变量加载

这些库都很常规,但关键不在于"用了什么库",而在于它们怎么组合

比如:

  • pytest 负责生命周期、参数化、fixture 注入

  • requests.Session 负责连接复用和 header 复用

  • YAML 负责把接口定义从代码中剥离

  • allure 负责结果可视化

  • jsonpath 负责复杂响应结构提取

  • db_helper 负责做接口与数据库的最终一致性校验

换句话说,技术栈本身并不"高级",真正高级的是分工清晰。

四、项目分层为什么这么拆

1)client/:只负责 HTTP 能力

client/base_client.py 做的是最底层的请求能力包装:

  • 管理 requests.Session

  • 拼接 base_url

  • 合并请求头

  • 打日志

  • 脱敏

  • 统一异常处理

它不应该知道"这是库存接口还是客户接口",它只关心"怎么发请求最稳"。

2)services/:只负责业务表达

services/业务系统A/inventory_service.py 里封装的是业务动作:

  • 查库存列表

  • 查商品属性

  • 查商品标签

这一层最重要的是把技术语言翻译成业务语言。

3)fixtures/:只负责把对象注入测试

测试用例不应该自己到处 new client、自己登录、自己手工取 token。

这些动作都应该由 fixture 统一完成。

4)common/:只负责通用能力

配置、鉴权、断言、数据库、变量存储,这些能力都是跨业务复用的,所以统一放在 common/

5)testcases/:只写业务验证逻辑

这是最终给测试工程师和业务方看的层。

它应该短、清晰、语义明确,而不是一堆"HTTP 细节堆砌"。

五、从一次请求看整个调用链

如果看一个真实用例,比如:

response = 业务系统A_inventory_service.list_inventory()

这个调用背后并不只是"发了一个 POST 请求"。

它实际经过了这样的链路:

  1. testcase 触发业务方法

  2. service 从 YAML 读取接口定义

  3. service 处理默认参数和覆盖参数

  4. client 拼接请求头和地址

  5. auth 注入 token 或业务头

  6. requests.Session 真正发起请求

  7. assertion 对响应结果统一校验

如果没有这样的分层,一次简单调用背后会变成:

  • 到处是 URL

  • 到处是 header

  • 到处是 token

  • 到处是重复断言

一旦维护周期变长,问题就会非常明显。

六、为什么 ConfigManager 是这个项目的核心之一

在很多项目里,配置文件往往只是用来放账号密码。但这个项目里的 ConfigManager 不是这么简单。

它做了两件很重要的事:

1)环境配置统一加载

通过 .envTEST_ENV 控制当前环境,读取 config/test.yamlconfig/uat.yaml 等文件。

这样做的好处是:

  • 测试环境切换更简单

  • 凭据和基础 URL 不需要硬编码

  • 本地跑、CI 跑、UAT 跑都可以复用同一套逻辑

2)API 定义统一加载

它会扫描 config/api_path/*.yaml,把多个系统的接口目录合并到同一个可查询的字典里。

这比把接口路径散落在代码里强很多,因为:

  • 新增接口时可以只补 YAML

  • 接口方法变更时更容易统一维护

  • 业务层调用时只需要记住 system.module.api 的 key

这个设计非常适合企业内部多系统场景。

七、为什么 Service 层比直接写请求更值得保留

如果测试用例里直接写请求,通常会变成这样:

requests.post(url, headers=headers, json=payload)

问题在于,这种写法无法沉淀。

Service 层的作用,是把这种低层细节包装成业务方法。

比如库存服务层:

def list_inventory(self, page_num=None, page_size=None, overrides=None, *, assert_response=True, **kwargs): ...

这个方法不是"为了多一层而多一层",而是为了让调用方表达更清楚的意图:

  • 我想查库存列表

  • 我可能想改分页参数

  • 我可能想临时覆盖条件

  • 我默认希望自动断言

这就比单纯传 URL、method、payload 更符合测试工程的使用习惯。

八、fixtures 为什么在这个项目里特别重要

fixtures/api_fixtures.py 是整个项目非常核心的一环。

因为它把"测试前准备"的逻辑从用例里剥离出来了。

例如:

  • sso_token:负责拿默认用户 token

  • operator_sso_token:负责拿操作员 token

  • 业务系统A_client:注入已登录的业务客户端

  • 业务系统A_inventory_service:注入库存服务对象

  • 业务系统B_client:注入 业务系统B的客户端

这样一来,测试用例根本不需要关心:

  • token 从哪里来

  • 登录流程怎么走

  • 客户端怎么初始化

  • 头部怎么拼

用例只需要关心"我今天要验证什么业务结果"。

这正是 pytest 的 fixture 体系最适合做的事情。

九、这个项目的架构原则是什么

如果我把这个项目的设计原则总结成几句话,会是这样:

1)配置驱动,而不是硬编码驱动

接口定义、环境配置、测试数据尽量外置。

2)业务表达优先,而不是请求表达优先

用例里写"查商品""查客户",而不是写一长串 URL。

3)公共能力集中管理

鉴权、断言、日志、数据库不要散落在各处。

4)默认流程统一,特殊场景可覆盖

常规用例走默认参数,特殊场景通过 overrides 处理。

5)让用例保持可读性

一个好用例应该是别人不用看实现,也能理解它在测什么。

十、小结

这篇文章讲的不是"怎么发一个 HTTP 请求",而是"怎么把接口自动化项目真正做成工程"。

这个项目的核心价值在于:

  • 请求能力统一了

  • 配置管理统一了

  • 鉴权统一了

  • 断言统一了

  • 用例表达方式也统一了

这会让后续的所有测试场景都站在同一套基础设施上。

补充:不是所有模块都需要抽象多一个service层,而是要看业务复杂度,服务层不是必须的,它是"当接口调用开始带业务语义和复用成本时"才值得抽出来的抽象。

  • 如果这个模块的接口调用逻辑,写在用例里 10 行就讲完
  • 且 后面几乎不会复用
  • 且 参数、鉴权、断言都很简单

那就没必要强行再抽一层 service。

反过来,如果出现下面任意一种情况,就值得抽:

  • 同一组接口会被多个用例反复调用
  • 一个业务动作要串多个底层接口
  • 默认参数、覆盖参数、路径参数替换比较多
  • 断言标准统一但又允许局部覆盖
  • 后续接口变更频繁,怕改坏很多用例
相关推荐
海兰9 分钟前
【 Python 量化交易】第8章:量化工具箱
开发语言·python
牛油果子哥q11 分钟前
C++大型项目工程精讲:CMake完整实战、静态库&动态库、模块化拆分、单元测试、gdb调试、性能工具、工程踩坑全解
开发语言·c++·单元测试
Dream Cosmos13 分钟前
C++ 多态上篇:从 virtual 到抽象类,彻底理解多态的使用
开发语言·c++
TheBestRucy14 分钟前
Python九阳神功之柒:数据分析三剑客·执剑问道
开发语言·python·数据分析
TheBestRucy28 分钟前
Python九阳神功之陆:数据库持久化与缓存·乾坤大挪移
数据库·python·缓存
qq_3391911432 分钟前
go cpu占比高排查,cpu100%排查,go pprof cpu命令
开发语言·后端·golang
西西弗Sisyphus37 分钟前
Qt 配置文件图标和文件版本信息
开发语言·qt
坐吃山猪40 分钟前
IDEA调试中Evaluate常用操作
java·python·intellij-idea