☰
电商接口自动化框架实战:Python+pytest+requests搭建与落地
2026/9/26 7:24:28 网站建设 项目流程

1. 为什么给泰和昌商城单独搭一套接口自动化框架:项目背景与测试痛点

接手泰和昌商城测试工作时,团队已经积累了不少手工测试用例,但每次版本迭代的回归压力依然很大。这个商城不是单纯的前台展示站,而是完整的电商闭环系统:会员模块、商品中心、购物车、订单流程、支付回调、优惠券核销、售后工单,还有后台的商家发货与库存扣减。光核心链路的接口就有五十多个,如果算上参数组合和状态流转,手工回归一次至少要花三到四个小时,每次迭代几乎都要全员投入。

1.1 这个商城的接口到底复杂在哪里

很多人以为电商接口测试就是"发个请求,看返回结不结构正确",实际上泰和昌这类商城的接口有几个显著特点,直接决定了测试方案的设计。

第一是强状态依赖。一个下单接口能否成功,取决于登录态是否有效、商品库存是否充足、用户是否领过优惠券、订单状态是否允许取消。接口之间不是孤立的,而是被业务状态串成一条链。第二是数据时效性。支付回调、优惠券过期、秒杀库存释放这些场景,如果依赖手工构造数据,效率极低而且容易漏测边界条件。第三是多环境并行开发。开发环境、测试环境、预发环境往往同时存在,同一套脚本要能跟着版本切换目标环境,否则自动化代码一换环境就罢工。

这些痛点决定了泰和昌商城的接口自动化不能只是"写几个脚本跑一跑",而是需要一个项目框架来统一管理:如何发送请求、如何维护登录态、如何配置多环境、如何组织用例和生成报告。

1.2 为什么优先做接口自动化而不是UI自动化

当时团队内部也讨论过要不要直接上UI自动化,我的判断是接口自动化优先。原因很直接:商城的UI页面变化频率远高于接口变化,前端一个按钮改位置、一个弹窗改样式,UI脚本就得跟着调整;而接口层只要契约不变,用例就是稳定的。更关键的是,接口自动化能更早地发现问题——前后端联调阶段,前端页面还没就绪,接口测试已经可以介入验证后端逻辑。

成本上也是明账。UI自动化一条用例的编写和维护成本大约是接口自动化的三到五倍,而商城系统里大部分业务逻辑错误发生在接口层,比如库存校验不严、优惠券叠加逻辑错误、支付回调幂等性不足。用接口自动化先把底层拦一遍,UI自动化只用覆盖少数关键用户路径,整体性价比高得多。

这个框架的定位也随之明确:日常回归验证、新需求冒烟测试、线上问题复现辅助、以及为性能测试准备可复用的脚本资产。

2. 框架技术选型的权衡:Python生态与Java方案的取舍

技术选型是搭框架的第一步,也是最容易被低估的一步。泰和昌商城后端本身是Java技术栈,当时团队里有同事提议用Java写接口自动化测试框架,理由是"和开发同语言,到时候排查问题方便,还能复用开发的一些工具类"。这个思路有一定道理,但最终我们选择了Python + requests + pytest的组合,这里面的取舍值得展开说说。

2.1 为什么不用Java框架

不是说Java不能做接口自动化,而是要考虑团队的维护成本和框架的迭代速度。Java体系下常见的Rest Assured、TestNG、HttpClient组合,确实功能强大,尤其适合那些对性能要求极高、需要深度定制、或者团队本身就是Java背景的场景。但泰和昌商城的QA团队当时主力技能是Python和Shell脚本,如果强行切Java,学习成本会消耗掉很大一部分精力。

另外,Java接口自动化框架的工程化程度高,意味着代码量也大。一个简单的请求封装,Java可能要写几个类,Python几十行就搞定了。对于框架初期的快速成型来说,这个差距非常明显。

2.2 Python生态里选什么组件

确定Python方向后,核心组件反而没什么纠结的:

  • pytest:测试用例编排和执行框架,fixture依赖注入、参数化、插件生态都非常成熟,比unittest灵活。
  • requests:业界最通用的HTTP客户端库,没有之一。会话管理、超时控制、代理配置都内置。
  • allure-pytest:报告生成。第一次跑通后生成HTML报告,团队直观看到通过率、失败原因、接口响应时间,比单纯看控制台输出有用得多。
  • PyYAML:测试数据用YAML管理,比写死在代码里好维护。
  • pytest-xdist:分布式执行用例,商城接口用例跑完一遍要不少时间,多进程能显著压缩回归时长。
  • pymysql / redis:接口测试不只是看接口返回,还需要校验数据库落库、缓存是否被正确修改,这两步是电商场景下必须的。

这套组合的好处是每个组件都很轻,出了问题社区资料也丰富,不需要自己造轮子。框架装完依赖不到10个包,换机器或者新同事接手都很容易跑起来。

2.3 直接买现成的接口测试平台不行吗

这个问题每次聊框架都会被问到。当时我们也评估过市面上的接口测试平台,最后放弃了,原因是自动化和"接口测试工具"的需求边界不一样。平台能解决"单个接口的调试和简单断言",但泰和昌商城更需要的是"业务链路的自动化编排"——比如先用A接口造一个优惠券,再用B接口验证领券,最后用C接口下单并校验优惠计算金额。这类跨接口的数据串联在平台上实现起来特别别扭,而且平台的定制能力有限,想接入自己的数据库校验逻辑和CI流程往往要踩不少坑。

自己搭框架看起来很费劲,实际上框架的骨架一旦立起来,后续扩建的速度是平台给不了的。这也是整个项目框架的核心判断:未来可扩展性的优先级高于初期省事。

3. 框架整体设计:三层结构加公共组件,目录怎么摆、各层管什么

框架的代码结构直接决定了团队协作的顺畅程度。泰和昌商城这套框架我采用的分层思路是:请求层、业务层、用例层三层分离,再加上公共组件和各环境配置目录。刚起步的团队建议直接照抄这个目录结构,避免后期越写越乱。

3.1 目录结构一览

taihechang_api_framework/ ├── config/ │ ├── dev.ini # 开发环境配置 │ ├── test.ini # 测试环境配置 │ ├── pre.ini # 预发环境配置 │ └── config.ini # 通用配置(超时、重试等) ├── common/ │ ├── request_client.py # 基于requests的统一请求封装 │ ├── token_manager.py # 登录态token缓存与管理 │ ├── db_utils.py # 数据库/Redis校验工具 │ ├── log_utils.py # 日志封装 │ └── assert_utils.py # 常用断言封装 ├── api/ │ ├── auth_api.py # 登录、注册等接口 │ ├── product_api.py # 商品查询接口 │ ├── order_api.py # 订单接口 │ └── pay_api.py # 支付接口 ├── business/ │ ├── order_flow.py # 下单全链路业务封装 │ └── coupon_flow.py # 优惠券业务流程封装 ├── testcases/ │ ├── test_auth/ │ ├── test_product/ │ └── test_order/ ├── data/ │ ├── users.yaml # 各环境测试账号 │ └── products.yaml # 测试商品数据 ├── reports/ # 测试报告输出目录 ├── conftest.py # pytest全局fixture ├── pytest.ini └── requirements.txt

这个结构的好处是:新同事接手时,先看api目录了解"系统有哪些接口",再看business目录了解"核心业务链路怎么跑",最后看testcases里的用例知道"测什么场景",每个层次的职责边界非常清楚。

3.2 三层各管什么,代码怎么写

请求层(common/request_client.py)是框架的地基。它统一封装了requests的get、post、put、delete方法,处理公共请求头、超时、重试、日志、响应状态码校验。业务层(business目录)负责把多个接口串成业务动作,比如"用户下单"这个动作内部会调用加购接口、获取默认地址接口、提交订单接口,对外只暴露一个create_order(user, product)方法。用例层(testcases目录)则只关注测试场景和断言,不关心接口细节。

举个例子,下单用例在testcases/test_order里看起来是这样的:

def test_create_order_success(order_fixture): """正常下单流程:加购-填地址-提交订单-校验订单状态""" order_no = order_fixture.create_order( user="test_user_01", product="phone_a", quantity=1 ) # 断言订单已创建,并校验数据库落库状态 assert_utils.order_exist(order_no) assert db_utils.query_order_status(order_no) == "SUBMITTED"

业务层封装了中间所有接口调用的细节,用例层只描述业务场景,这样即使底层接口变了,比如下单接口从post改成put或者URL变了,只需要改business目录,用例几乎不用动。

3.3 fixture的组织方式

conftest.py里集中管理全局fixture,例如登录态、环境配置、数据库连接。这里要特别强调一个实践原则:不是每个用例都从头登录、从头造数,而应该在fixture里做会话级缓存,把耗时操作放到session作用域。后面单独讲登录态处理时会展开。

4. 环境自动切换的实现:开发、测试、预发环境一套代码跑到底

泰和昌商城的环境管理一开始其实很混乱。最开始写脚本的时候,每个脚本里都硬编码了环境地址,比如BASE_URL = "https://test-api.xxx.com",换个环境就得全局替换URL,一不小心漏一处,脚本就跑到错误的环境里去了,测试数据全乱套。这个问题在接口自动化里非常典型,配置自动切换环境是框架成熟度的一个分水岭。

4.1 配置文件的组织方式

解决办法是把环境信息从代码里抽出来,放到独立配置文件里。config目录下按环境拆成dev.ini、test.ini、pre.ini,里面包含当前环境的Host、测试账号、数据库连接信息,以及一些业务参数。

dev.ini的内容大致是这样的:

[server] host = https://dev-api.taihechang.com timeout = 10 [user] default_account = dev_test_001 default_password = 123456 [database] host = dev-db.taihechang.com port = 3306 user = tester password = testpass db = taihechang_mall [redis] host = dev-redis.taihechang.com port = 6379 db = 0

test.ini、pre.ini结构完全一致,只是值不同。配置文件放在config目录下统一管理,配合Git分支,环境变更时能追踪到改动记录。

4.2 pytest启动参数与fixture读取逻辑

环境切换的实现核心在两个地方:一是pytest启动时通过命令行参数指定环境,二是conftest.py里写一个session级别的fixture读取对应的配置文件。

在conftest.py中利用pytest的addoption来注册环境参数:

import configparser import pytest def pytest_addoption(parser): parser.addoption( "--env", action="store", default="test", help="选择运行环境: dev / test / pre / prod" ) @pytest.fixture(scope="session") def env_config(request): env = request.config.getoption("--env") config_file = f"config/{env}.ini" parser = configparser.ConfigParser() parser.read(config_file, encoding="utf-8") return parser

运行时这样指定环境:

pytest testcases -m smoke --env=test pytest testcases --env=pre

在Jenkins或者GitLab CI里,也可以把这个参数暴露成构建参数,由运维或者测试人员在触发构建时选择运行环境。这样一套代码就能在不同环境之间无缝切换,代码里完全不需要出现任何硬编码的URL。

4.3 多环境切换的隐藏细节

环境切换看起来简单,实际落地时有几个容易踩的坑。

配置文件的路径问题。pytest的rootdir不同会导致相对路径失效,建议在conftest.py里通过os.path.dirname(__file__)动态拼接配置文件路径,而不是直接写相对路径。不然本地命令行跑得好好的,一到Jenkins就报找不到配置文件。

数据库连接的环境隔离。商城接口测试经常要校验数据库,dev环境的数据库和test环境的数据库不能共用连接配置。所以db_utils里的连接参数必须从env_config读取,不能写死。同时不同环境的测试数据要设计成不同前缀,比如dev环境账号统一用dev_开头,test环境用test_开头,避免多个环境共享同一套数据导致污染。

Redis缓存的情况更隐蔽。泰和昌商城的商品库存、登录token会缓存在Redis里,接口测试如果直接修改了Redis数据,要确保操作的是当前环境对应的Redis实例。一开始踩过坑,在预发环境排查问题时误连接了测试环境的Redis,导致测试环境的数据被污染,排查了半天才反应过来是连接错了实例。所以db_utils和redis工具类都要做成从env_config动态初始化,每次执行时打印当前环境标识,日志里能一眼看出连的是哪个库。

5. 登录态与数据依赖:电商接口自动化最难啃的两块骨头

如果环境切换是框架的门槛,那登录态维护和数据依赖管理就是电商接口自动化的核心难点。这两个问题不解决,框架能跑但跑不稳,用例运行时间一长就开始随机失败,最后大家会失去对自动化结果的信任。

5.1 登录态管理:从每次登录到token缓存复用

泰和昌商城的接口鉴权采用的是JWT token机制,token有效期是一小时,用户信息变更后还需要刷新权限。

如果每个用例都执行一次登录接口,有两个问题:一是登录接口本身有频率限制,用例一多就会出现登录失败导致的连带报错;二是登录耗时占比大,跑一百条用例光登录就要花不少时间。

最终方案是做一个token_manager模块,配合conftest.py的session级fixture来实现token缓存复用:

@pytest.fixture(scope="session") def session_token(env_config): token = token_manager.get_valid_token(env_config) return token

token_manager做的事情是:优先从本地缓存文件读取token,判断是否过期;如果有效就直接返回,如果过期才重新登录并更新缓存。同时把token过期时间也存下来,留一个提前量,比如过期前60秒就视为无效,保证并发执行时用例拿到的一定是可用的token。

但这里还藏着一个坑:pytest-xdist多进程执行时,每个worker是独立的进程,session级的fixture在每个worker里各执行一遍,也就是说会有多个进程同时去检查缓存。如果大家同时发现token过期了,就会同时发起登录请求。所以token写入缓存文件时必须加进程锁,避免多个worker同时写文件导致token错乱。这个细节花了我们不少时间才定位清楚,现象就是用例随机报401,但单独跑又是过的。

5.2 数据依赖串联:造数、传参、清理

商城业务用例之间经常存在数据依赖,比如优惠券下单用例需要先领券、先登录、先有商品。如果在每条用例内部都从前置第一步做起,用例会变得冗长且重复。更好的方式是业务层封装加上fixture组装。

以优惠券下单流程为例:

@pytest.fixture def coupon_order_data(session_token, env_config): """创建优惠券下单需要的完整前置数据""" user = user_pool.get_user(env_config) coupon_id = coupon_api.create_coupon(session_token) # 创建优惠券 product_id = product_api.get_available_product(session_token) # 获取可下单商品 return { "user": user, "coupon_id": coupon_id, "product_id": product_id }

用例里直接引用这个fixture,然后执行领券、下单、断言优惠金额。这样用例逻辑集中在一个业务场景的验证上,前置数据的复杂度被隔离在fixture里。

数据传递还有一个常见问题:需要跨用例传递的变量。泰和昌商城的订单号、支付流水号这类数据,可以在业务层里记录到当前上下文,然后通过pytest的request对象或共享字典传给后续断言。但需要注意的是,pytest-xdist并行执行时,共享字典是进程隔离的,跨用例传递只适用于同一个worker内执行的用例。所以如果需要强依赖上下游执行顺序的用例,不能依赖随机并发顺序,要在pytest.mark依赖标记或者用例分层上控制好执行顺序。

数据清理也是一个容易被忽略的点。造出来的优惠券、订单如果都留在测试环境,跑一个月环境就变成垃圾场了。泰和昌商城的测试环境我们约定了一套清理策略:测试账号统一用tester_前缀,造的优惠券编码统一用AT_前缀,Redis缓存键统一带at_前缀,定时任务去清理。写用例的时候,每条用例结尾最好把创建出来的核心数据做标记,方便后续识别。

5.3 接口返回之外的校验:数据库、Redis与ES

接口自动化最常见的错误理解是只看接口返回。泰和昌商城的实战经验告诉我,真正的断言至少要三层:接口返回结构、数据库落库、缓存一致。

比如下单成功后,接口返回可能只是{"code":0,"data":{"orderNo":"xxx"}},但核心问题往往在后面:订单金额在数据库是否计算正确、库存扣减是否完成、Redis中的库存缓存是否同步更新。有些搜索相关的功能,还会涉及ES的索引更新,下单生成的商品如果参与搜索,需要到ES里查一下是否能即时搜到。

我们专门封装了db_utils和es_utils,支持在测试用例里做数据层的二次校验。这一步的价值不只是发现问题,更是定位问题的能力:接口返回对了,但数据库状态不对,那问题很可能出在服务端逻辑而不是接口层,能直接反馈给开发一个明确的排查方向。

6. 落地过程中的踩坑复盘与后续迭代:那些文档里不会写的细节

框架从第一版能跑到真正稳定使用,中间经历了不少问题。有些坑属于"遇到了才知道是坑",写出来可以帮助后来者少走弯路。

6.1 接口返回结构不统一

泰和昌商城的接口并不完全遵循同一套返回格式,有的接口直接返回业务数据,有的包一层code/message/data,还有的字段命名一会儿下划线一会儿驼峰。如果断言层直接按统一格式写,就会频繁翻车。

我们的做法是在assert_utils里做了一层字段适配,读取接口返回时先判断外层结构,再通过jsonpath表达式提取需要断言的字段。这样用例层只需要写"我要断言什么",不需要关心接口返回的原始格式差异。类似的还有状态码判断,某些老接口不遵循2xx语义,比如登录失败也返回200,这就要以业务code为准而不是HTTP状态码为准。

6.2 弱断言导致漏报漏测

框架刚跑起来时,出现过不少"接口返回成功但金额算错了"的漏报。最初的断言只校验了code==0,没校验响应里的关键数据。后来把所有核心接口的断言规范升级成"状态码 + 核心字段值 + 数据库状态"三层校验,甚至对下单这种关键接口加了返回结构的jsonschema校验,一旦返回字段缺失或者类型不对,用例立刻失败。这一步明显提升了框架发现线上问题的能力。

6.3 pytest-xdist并发造数冲突

引入pytest-xdist之后出现了一个奇怪的问题:用例在单进程执行时全部通过,一开分布式执行就随机失败。排查下来是多个worker同时生成手机号,时间戳粒度不够,导致两个worker生成了同一个手机号,注册接口幂等性校验直接让它其中一个失败。

解决方案是造数时引入全局唯一的标识,除了时间戳外加上进程号和随机数,同时手机号生成逻辑单独抽成一个工具函数,所有需要手机号的地方统一调用,杜绝各处自己造数。

6.4 框架迭代的路径建议

如果从零开始搭一套接口自动化框架,不建议一上来就追求大而全。我们走过的路径是:第一步,先把核心链路用脚本跑通,不讲究结构,目的是确认"接口自动化在泰和昌商城可行";第二步,把重复的代码抽成公共模块,引入pytest和配置文件;第三步,完善多环境切换、登录态管理、数据库校验这三个核心能力;第四步,接入allure报告和CI流水线,让自动化跑完主动通知到群里;最后才是横向铺开用例覆盖度和分布式执行。

每一步都有一个明确的收益,能驱动团队持续投入。反过来如果一开始就设计十多个模块,反而容易陷入过度工程,框架写了一个月还没跑通一条用例,大家就失去了信心。

6.5 给团队落地的一点心得

最后分享一点个人体会。框架搭起来之后,最大的收益不是"省了多少手工回归时间",而是让测试团队在版本迭代里有了主动权。以前版本发版前的风险是模糊的,现在跑一遍自动化,哪条链路挂了一目了然;开发改动了接口字段,自动化回归能立刻反馈影响范围。

要继续扩展的话,有两个方向我认为比较有价值:一是把框架里的业务层能力做成一个简单的命令行工具,让开发和测试都能快速创建测试数据,这个对日常联调很有帮助;二是把常见的线上异常场景,比如支付回调超时、重复支付、库存超卖这些,转成故障注入的自动化用例,让框架从"回归工具"升级成"稳定性保障工具"。这一步做完,接口自动化的价值就完全超出最初的预期了。

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

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

立即咨询