Python+Requests从零搭建接口自动化测试框架:封装、断言、数据驱动与CI实践

发布时间:2026/10/10 11:36:10
Python+Requests从零搭建接口自动化测试框架:封装、断言、数据驱动与CI实践 1. 为什么要用 Requests 搭接口自动化框架做接口自动化这件事最容易陷入一种错觉能用 Requests 调通一个接口就等于有了一套自动化测试框架。我面试时经常遇到这种情况简历上写着熟悉接口自动化追问到底层封装了什么、超时重试怎么处理、断言失败怎么定位就讲不清楚了。真正跑过一两个月的业务回归之后你才会明白框架的核心不是那几行 HTTP 调用而是它能不能让用例好写、好维护、好排查。这篇要讲的就是用 Python Requests 从零搭一套基础接口自动化测试框架覆盖选型、目录结构、核心封装、断言、数据驱动、报告与 CI也把我在实际维护过程中踩过的 429 限流、编码乱码、依赖版本这些真实坑一并交代清楚。1.1 三选一urllib、Requests、httpx我站 Requests 的理由很多人一开始会用 Python 标准库里的 urllib我也这么干过。urllib 的问题不是不能写而是太啰嗦重定向要手动处理、代理需要自己建 opener、Cookie 管理麻烦、错误异常类型也不够直观。写三五个接口脚本还能忍一旦到几十个接口、几百条用例那一层一层的 try-except 和状态判断会变得非常痛苦。后来换到 Requests第一感受是 API 设计确实贴近直觉requests.get(url, params..., headers...)一行拿到响应.json()直接解析Session天然支持 Cookie 和连接复用超时、代理、SSL 验证都有明确的参数。它本质上是对 urllib3 的封装生态特别成熟网上资料、踩坑案例、第三方集成方案一搜一大把团队新成员上手成本低这是选型时最实际的优势。也有些同事推荐 httpx它能异步、支持 HTTP/2听起来很现代。但对接口自动化测试这类场景来说异步和 HTTP/2 实际上收益有限反而团队里大多数人还要重新学一遍 API 习惯。再加上 Requests 跟 pytest、Allure 这些工具链的配合资料最多我最终做接口自动化的基础框架时还是会选 Requests。性能从来不是接口自动化测试的主要矛盾稳定、可读、生态够用才是。1.2 一套框架到底“框架”了什么如果只是写脚本那叫“请求调用”不叫“框架”。我认为一套基础接口自动化框架至少要承担这几件事统一请求入口所有请求走同一个方法才可能做日志埋点、耗时统计、鉴权注入和统一重试。统一配置管理base_url、超时时间、环境切换不能散落在用例代码里应该有专门的配置层。统一鉴权处理token、Cookie 由框架负责注入业务用例里不出现重复的登录和鉴权代码。统一断言能力状态码、业务码、字段校验都要有封装报错信息格式一致不然每个人写的 assert 风格都不一样。统一报告输出用例失败时需要留下足够信息配合 Allure 或 pytest 日志能快速定位问题。数据驱动能力测试数据和用例逻辑分离同一接口几十组参数不需要复制粘贴几十个函数。说白了框架的价值是把“写请求”变成“维护用例”。业务测试同学拿到一个模板就能补充用例而不是每次都要跑去看那段复杂的 requests 代码。1.3 这套框架的适用边界它在什么场景下不背锅这套框架最适合的场景是服务端接口回归、冒烟测试、前后端联调自测尤其是中小团队需要快速搭建一套成本可控的自动化体系时它非常合适。但我必须把边界说清楚免得你拿它去干不合适的事高并发性能压测别用它那是 Locust、JMeter 的领域Requests 的同步模型做压测很容易变成测试工具自身成为瓶颈。UI 自动化测试它管不了接口层和页面层是两回事页面操作该选 Playwright 或 Selenium。如果被测服务大量使用长连接或者流式接口Requests 用起来会别扭这属于选型问题不要硬套。框架不是万能的但它能解决 80% 的回归和冒烟需求这就够了。2. 环境准备与工程骨架目录结构决定维护成本环境准备看起来简单实际上第一次跑项目的人最容易在这里卡住。Python 版本、虚拟环境、依赖冲突、pip 权限问题每一项都会让项目在一个新同事电脑上多花半小时。与其后面逐个解释不如一开始就把规范定好。2.1 Python 环境、依赖与第一次 pip 报错我建议直接用 Python 3.9 以上版本官方下载安装时勾选 “Add Python to PATH”然后创建虚拟环境。虚拟环境不是可选项是必须项因为它能隔离不同项目的依赖避免“我本地是好的你那边装不上”这种经典问题。创建方式很简单python -m venv .venvWindows 下面进入虚拟环境是.venv\Scripts\activateLinux 或 macOS 是source .venv/bin/activate。激活之后终端提示符会多出(.venv)然后再装依赖pip install requests pytest allure-pytest如果你在 Windows 下遇到的提示是Defaulting to user installation because normal site-packages is not writeable先不用急着加--user。这个提示通常意味着当前 Python 安装在系统目录、权限受限。硬加--user虽然能装上但包会散落到用户目录后面升级和卸载都会很别扭。正确做法是回到项目目录创建虚拟环境把依赖都装到项目自己的环境里。依赖装好后建议固化一份 requirements.txtrequests2.31.0,3.0 pytest7.0,9.0 allure-pytest2.13.0为什么要锁版本接口自动化项目一旦跑起来最怕的不是代码 bug而是某天依赖自动升级后行为变了。requests 2.31 之后底层 urllib3 升到 2.xRetry 参数从method_whitelist变成了allowed_methods如果旧代码直接搬过去就会 TypeError。锁版本能少踩很多这种坑。2.2 工程目录这样分才不会被业务用例淹没我见过不少项目把所有测试代码堆在一个test_api.py里几百行之后谁都看不动。一个可维护的接口自动化工程目录结构至少要有这样几层api_test_frame/ ├── config/ │ ├── __init__.py │ ├── settings.py │ └── test_env.yaml ├── core/ │ ├── __init__.py │ ├── base_api.py │ ├── session_manager.py │ └── assertion.py ├── data/ │ ├── login_cases.json │ └── order_cases.xlsx ├── testcases/ │ ├── __init__.py │ ├── conftest.py │ ├── test_login.py │ └── test_order.py ├── utils/ │ ├── __init__.py │ ├── logger.py │ └── read_data.py ├── report/ ├── requirements.txt └── pytest.ini这个结构的核心思路是让“会变化的”和“不会变化的”分开。config 存放环境和全局配置基本不动core 是框架层封装请求、断言属于本项目自己的基础设施testcases 放业务用例最常改动data 放用例数据测试和产品同事都能看utils 放日志、文件读取等通用能力。有了边界之后新增一个业务接口的改动路径通常就两处在 data 里加用例数据在 testcases 里加 py 文件或函数如果底层请求逻辑没问题core 完全不用碰。这种约束带来的收益项目跑两个月之后会非常明显。2.3 pytest 配置和 conftest 的职责pytest 是这套框架的执行引擎它的配置文件建议放在项目根目录名字固定为 pytest.ini内容类似[pytest] testpaths testcases addopts -q markers smoke: 冒烟测试用例 full: 全量回归用例testpaths 指定 pytest 去 testcases 目录收集用例addopts 设置默认参数-q是精简输出如果想看详细报告可以追加-vmarkers 用于标记用例比如pytest.mark.smoke可以单独跑冒烟集。conftest.py 是 pytest 的扩展点用来放 fixture。要注意它属于框架层不要写具体业务用例。在这一层我可以放一个全局的 BaseApi fixture再放一个 session 级别的登录 fixture让所有用例共享登录态。fixture 的 scope 需要按需选登录态用scopesession临时数据或者每条用例独立的清理工作用scopefunction。放错 scope 最常见的后果是目标接口做了数据隔离用例间互相污染查起来非常痛苦。3. 请求层封装session、超时、重试、日志全配齐这是框架里最核心的一层也是最容易被低估的一层。网上很多示例代码是每个用例里直接requests.post()一把梭看起来灵活实际上一旦需要全局统计、限流重试、日志留痕你就要去改几十个文件。正确的做法是把请求行为收敛到一个类里所有用例只跟这个类打交道。3.1 先封一个 BaseApi而不是上来就 get/post我先给出一版最简的 BaseApi它在我的多个项目里都是作为基层存在import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry from utils.logger import get_logger class BaseApi: def __init__(self, base_url, timeout(3, 10)): self.base_url base_url.rstrip(/) self.timeout timeout self.session requests.Session() self.logger get_logger(api) self._setup_retry() def _setup_retry(self): retry Retry( total3, backoff_factor1, status_forcelist[429, 500, 502, 503, 504], allowed_methods[GET, POST, PUT, DELETE], ) adapter HTTPAdapter(max_retriesretry) self.session.mount(https://, adapter) self.session.mount(http://, adapter) def request(self, method, url_suffix, **kwargs): kwargs.setdefault(timeout, self.timeout) url f{self.base_url}/{url_suffix.lstrip(/)} resp self.session.request(method, url, **kwargs) self._log_request(method, url, kwargs, resp) return resp def get(self, url_suffix, **kwargs): return self.request(GET, url_suffix, **kwargs) def post(self, url_suffix, **kwargs): return self.request(POST, url_suffix, **kwargs) def _log_request(self, method, url, kwargs, resp): self.logger.info( [%s] %s - %s (%.2fs), method.upper(), url, resp.status_code, resp.elapsed.total_seconds(), )这里有两个容易被忽略的设计。第一timeout 用了元组(3, 10)第一个值是连接超时第二个是读超时。连接超时只负责建立 TCP 连接读超时才真正限制服务端响应时间。我见过有人直接写timeout5其实那是同时作用于连接和读取一旦接口慢在响应阶段测试很容易误判。第二HTTPAdapter 的 mount 要同时挂 http 和 https 两套因为测试环境有时候是 http 内网地址只挂一个切换环境时重试就失效了。3.2 Session 复用与 token 注入的正确姿势requests.Session最大的价值不是“看起来高大上”而是能复用底层 TCP 连接保持 Cookie 状态。在一个完整的业务链路测试里登录之后会连着调十几个带鉴权的接口如果每个接口都新建一个连接不仅慢而且容易触发服务端的连接频率限制。token 注入我建议这样做在 BaseApi 里提供一个登录方法登录成功后直接把 token 更新到 session.headers后续所有请求自动携带业务用例里就不用再写鉴权头。def login(self, username, password): payload {username: username, password: password} resp self.post(/login, jsonpayload) body resp.json() token body[data][token] self.session.headers.update({Authorization: fBearer {token}}) return resp这样做的核心原因是“统一”。如果每个用例都自己拼Authorization一旦 token 字段名调整或者加签名所有用例都要跟着改那框架就失去意义了。当然也要注意 token 过期的问题。基础做法是在 conftest.py 里放一个 session 级别的 fixture先登录再跑全部用例如果测试链路很长导致 token 中途失效可以在请求层识别到 401 时触发一次重新登录再重放一次原请求这个逻辑同样只放在框架层不污染用例。3.3 429 限流与重试策略别再傻等十几分钟接口测试跑到一半突然报urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host..., port443): Max retries exceeded with url: ... (Caused by ResponseError(too many 429 error responses))这是我在实际项目里见过最多的限流报错之一也就是标题热搜里提到的exceeded retry limit, last status: 429 too many requests。429 表示服务端明确告诉你“请求太频繁”多数是网关或风控策略在多长时间内限制了请求次数。出现这个问题的原因通常不是框架 bug而是测试节奏太快用例并发过高、没有设置请求间隔、或者某个接口不小心进入了重试死循环。我在 BaseApi 里用 Retry 做的是最基础的一层兜底参数如下Retry( total3, backoff_factor1, status_forcelist[429, 500, 502, 503, 504], allowed_methods[GET, POST, PUT, DELETE], )Retry 的退避时间是按照backoff_factor * (2 ** (retry_count - 1))计算的backoff_factor1时第一次重试等 1 秒第二次等 2 秒第三次等 4 秒总等待约 7 秒。这个策略对于偶尔一次的风控足够友好但如果服务端持续 429重试只会加大服务端压力这时候应该让测试停下来人工介入而不是无限重试。这里有两个经验值得单独说。第一POST 这类非幂等请求重试需要谨慎。重试可能导致订单重复创建、状态重复变更。如果被测环境不敏感可以像我一样把 POST 加进 allowed_methods方便快速拿到结果但如果服务端没有幂等设计建议只重试 GET或者针对特定接口单独调整重试策略。第二如果 429 响应里带了Retry-After头那服务端已经告诉你应该等多少秒了此时 Retry 的固定退避策略就不够聪明。可以手动读取头信息等够时间再继续而不是套通用重试。另外提醒一句尽量不要在用例里写死time.sleep(5)这种魔法数字。正确做法是控制用例并发或只在关键节点使用重试机制。单纯靠 sleep 会让测试时间变得又长又不稳定。3.4 日志与脱敏排查问题靠的是现场留痕没有日志的接口测试框架出问题的时候等于大海捞针。我只记录几条核心信息请求方法、请求 URL、状态码、耗时以及响应体摘要。完整响应体可能会非常大全部打进日志会让日志文件迅速膨胀一般只保留前 500 字符。日志里还必须要做脱敏。测试账号的密码、登录接口返回的 token 都不能原样打印否则日志一旦被共享账号信息就泄露了。具体做法是在打印前对 dict 做一层处理import logging def get_logger(nameapi): logger logging.getLogger(name) if not logger.handlers: logger.setLevel(logging.INFO) handler logging.FileHandler(report/api_test.log, encodingutf-8) handler.setFormatter( logging.Formatter(%(asctime)s [%(levelname)s] %(name)s - %(message)s) ) logger.addHandler(handler) return logger def safe_body(body): if isinstance(body, dict): body dict(body) for key in (password, token, secret): if key in body: body[key] *** return body日志文件用encodingutf-8是必须的尤其是 Windows 环境默认编码可能是 GBK中文响应体写进去直接乱码。至于日志级别我建议默认 INFO只记录摘要排查问题时临时把级别调到 DEBUG打印完整请求头和响应体。4. 断言设计框架好不好用看用例报错能不能一次看懂很多人觉得断言就是assert resp.status_code 200写完之后用例确实能跑但失败的时候一脸懵因为报错信息里看不到实际响应内容。断言这一层如果设计得不好排查一个接口问题可能比手工测还慢。4.1 用例模板一条可读的用例长什么样我习惯把接口用例抽象成统一结构字段包括case_id、title、url_suffix、method、headers、body、expect_status、expect_business_code、expect_fields。一个 JSON 用例文件大概是这样的[ { case_id: login_001, title: 登录接口-正常用户名密码, url_suffix: /api/v1/login, method: POST, body: { username: tester, password: 123456 }, expect_status: 200, expect_business_code: 0, expect_fields: { data.token: is_not_blank } }, { case_id: login_002, title: 登录接口-密码错误, url_suffix: /api/v1/login, method: POST, body: { username: tester, password: wrong }, expect_status: 200, expect_business_code: 10001, expect_fields: { data: is_none } } ]统一模板的最大价值是可遍历、可生成。pytest 能把每一条 case_id 作为用例 ID报告里看到login_001就知道是哪条而不是只有test_login[case0]这种毫无意义的名字。4.2 分维度断言HTTP 码、业务码、字段校验断言要分两层看。第一层是 HTTP 状态码这层代表网络协议层的成功通常 200、201 都在期望范围内。第二层是业务码这层才是接口真正成功与否的标志很多系统接口始终返回 HTTP 200但 body 里的code可能是 10001 代表业务失败。我最常用的一组断言函数是这样def assert_status(resp, expected200): actual resp.status_code assert actual expected, ( fHTTP 状态码校验失败期望 {expected}实际 {actual} fbody{resp.text[:500]} ) def assert_business_code(resp, expected0): body resp.json() actual body.get(code) assert actual expected, ( f业务码校验失败期望 {expected}实际 {actual} fbody{safe_body(body)} ) def assert_field(resp, expr, predicateis_not_blank): body resp.json() value resolve_expr(body, expr) # 按点号路径取值如 data.token if predicate is_not_blank: assert value not in (None, ), f字段 {expr} 为空body{safe_body(body)} elif predicate is_none: assert value is None, f字段 {expr} 应为空实际 {value}body{safe_body(body)}业务码断言是这些里面最重要的。你自己写接口自动化时可以把# fmt: off这种缩进问题放一边但一定不要把断言只停留在 HTTP 200。我在项目中见过一个经典事故服务端配置中心挂了所有接口返回 HTTP 200 但 body 里 code50002外层断言没做业务码校验整条回归全绿隔天业务才反馈问题。从那以后凡是我经手的框架状态码和业务码的断言必须同时出现。复杂响应体做整体结构校验时我建议引入jsonschema因为它能对嵌套结构、字段类型、是否允许额外字段做严格约束比手写 if 判断可靠得多。但如果只是校验几个关键字段上面这套轻量函数就够用没必要为了“显得专业”引入一套重武器。4.3 断言失败时把“现场”打出来pytest 本身有很好的失败输出机制但如果你写的是assert resp.json()[code] 0当断言失败时pytest 只会告诉你这个比较表达式为 False不告诉你 body 里到底返回了什么。所以你实际排查时还得手动改代码再跑一次效率极低。我的经验是让断言函数自己携带现场信息把期望值、实际值、响应摘要拼进断言消息里。这样失败时 pytest 输出里就能看到类似断言失败: 业务码校验失败期望 0实际 10001body{code: 10001, msg: 用户名或密码错误, data: None}看到这行输出基本不用再翻日志就能定位问题。另外推荐 pytest 的两个参数-l可以让失败时打印用例内局部变量--tblong能显示完整 traceback。跑用例时我经常用这条命令pytest -x -q -l testcases/test_login.py-x表示遇到第一条失败就停调试阶段非常有用。全量回归时再拿掉让所有用例跑完。5. 数据驱动把测试数据和代码拆开用例量翻倍也不慌数据驱动说起来不复杂但它是把框架从“能跑”推向“能维护”的关键一步。核心思想就一句话测试数据和用例逻辑分离同一套代码跑不同的输入和预期。5.1 为什么数据外部化是刚需举个实际场景登录接口正常、密码错误、账号锁定、验证码过期、参数缺失、服务端异常这些场景加起来少说也有十几种。如果每组数据都写成一个测试函数你会得到十几个几乎一模一样的函数区别只是参数和期望值。以后新增一个场景又要复制一个函数代码量膨胀很快。改成数据驱动之后要新增一条用例只需要在 data 文件里加一条 JSON 数据测试函数本身完全不用动。这个好处不仅是代码量少更重要的是让非代码参与者也能维护用例。产品、测试同事能直接看 JSON 文件知道某条用例覆盖了什么场景而不用去读 Python 代码。5.2 JSON 用例加载与 parametrize 接入数据加载我通常放在 utils/read_data.pyimport json from pathlib import Path BASE_DIR Path(__file__).resolve().parent.parent DATA_DIR BASE_DIR / data def load_json_cases(filename): filepath DATA_DIR / filename with open(filepath, r, encodingutf-8) as f: return json.load(f)这里有两个细节值得注意。一是路径不能写成相对路径data/login_cases.json因为 pytest 在不同目录下执行时相对路径的基准目录会变化容易找不到文件用Path(__file__).resolve()基于当前文件位置往上找才是稳定做法。二是读取文件时要显式指定encodingutf-8否则 Windows 环境下中文用例数据读出来会有编码问题。然后通过 pytest 的 parametrize 把用例数据接入import pytest from core.base_api import BaseApi from utils.read_data import load_json_cases from core.assertion import assert_status, assert_business_code cases load_json_cases(login_cases.json) pytest.mark.parametrize(case, cases, ids[c[case_id] for c in cases]) def test_login(base_api: BaseApi, case): resp base_api.post(case[url_suffix], jsoncase.get(body, {})) assert_status(resp, case[expect_status]) assert_business_code(resp, case[expect_business_code])第二个参数 ids 的作用是让测试结果显示成login_001、login_002而不是case0、case1。你在终端和报告里看到清晰用例名排查问题时能省很多时间。如果用例数据储存在 Excel就需要用 openpyxl 读取注意三点第一数字单元格读出来可能是 float比如 id 变成10001.0需要转成 int 或 str第二空行要及时跳过避免把 None 当成参数传进用例第三日期类型的单元格可能变成 datetime 对象要在读取阶段统一转成字符串。Excel 的好处是业务人员熟悉劣势是版本管理不如 JSON 方便团队怎么选取决于实际情况。5.3 用例间数据传递token、依赖响应怎么串接口用例之间经常有依赖典型例子是创建订单接口返回 order_id支付订单接口又需要这个 order_id。新手最容易犯的错误是依赖用例执行顺序比如让test_pay_order排在test_create_order后面靠 pytest 按文件顺序执行。这样一旦用-k筛选只跑支付用例或者用例失败被跳过后面的用例全崩。正确的做法是把跨用例数据放到一个轻量上下文里我经常用一个简单的 Context 类class Context: order_id None创建订单的用例执行成功后把结果写进 Contextdef test_create_order(base_api: BaseApi): resp base_api.post(/order/create, jsonsome_data) assert_business_code(resp, 0) Context.order_id resp.json()[data][order_id]支付订单的用例在读取前先校验def test_pay_order(base_api: BaseApi): assert Context.order_id is not None, 没有可用的 order_id请先执行创建订单用例 resp base_api.post(/order/pay, json{order_id: Context.order_id}) assert_business_code(resp, 0)这样做的核心原则是“用例运行时数据被显式传递而不是靠执行顺序隐形传递”。不过我也要提醒Context 里的全局状态能少用就少用。最好的状态是每个用例都能自行准备前置数据比如创建订单用例本身返回 order_id 后直接在同一个用例里调用支付接口完成链路这比跨用例传数据更让人安心因为单个用例可以独立重跑、独立失败了。6. 报告与 CI框架的最后一公里框架跑通之后下一步要解决的是“结果怎么呈现”和“怎么自动跑”。如果每天要靠人工手动执行一次那自动化测试的意义就少了一半。报告和 CI 是框架从“自己用”走向“团队用”的最后一公里。6.1 Allure 报告让用例结果能讲人话pytest 自带的输出适合开发阶段看但给团队和领导看就不够直观。Allure 是我用得最顺手的报告框架它能按 feature 和 story 聚合用例展示成功、失败、跳过数量还能保留请求日志作为附件。先安装依赖pip install allure-pytest然后需要在本地安装 Allure 命令行工具它依赖 Java 环境安装完成后用以下命令跑用例并生成报告pytest testcases --alluredirreport/allure-results allure generate report/allure-results -o report/allure-html --clean allure open report/allure-html--alluredir是指定原始结果目录allure generate把原始结果渲染成网页版报告--clean表示每次生成前清空旧报告避免历史结果污染新报告。让报告更可读的另一个做法是给用例加装饰器。例如import allure allure.feature(登录模块) allure.story(正常登录) allure.title(登录接口-正常用户名密码) def test_login(base_api: BaseApi): ...feature 可以理解成模块story 是子场景title 是在报告里展示的用例名。有了这些之后测试结果页面可以按模块分组点进去能看到每一条用例的请求和响应失败时排查效率会高很多。6.2 在 CI 里跑起来流水线配置示例CI 的接入不一定要一步到位但至少应该让流程是“推代码 → 自动跑接口用例 → 产出报告”而不是“需要在某台电脑上挂着跑”。下面是一份 GitLab CI 的最小配置放在项目根目录.gitlab-ci.ymlapi-test: stage: test tags: [python-runner] script: - python -m venv .venv - . .venv/bin/activate - pip install -r requirements.txt - pytest --alluredirreport/allure-results - allure generate report/allure-results -o report/allure-html --clean artifacts: paths: - report/allure-html expire_in: 7 days only: - main这里有几个实战点。激活虚拟环境的命令不同系统不一样Linux 和 macOS 是. .venv/bin/activateWindows 的 CI runner 要改成.venv\Scripts\activate写流水线时先确认 runner 的操作系统类型。测试环境地址不要写死在代码里我习惯用环境变量控制。比如在 settings.py 里import os ENV os.getenv(TEST_ENV, dev) ENV_CONFIG { dev: {base_url: http://192.168.1.10:8080, timeout: (3, 10)}, staging: {base_url: https://staging.example.com, timeout: (5, 15)}, }CI 里跑 staging 环境时只要设置export TEST_ENVstaging再执行 pytest代码不需要改。6.3 踩坑实录编码、路径、环境变量、SSL 告警框架搭完不代表就没事了实际运行一段时间后你会遇到一些看起来很怪的问题我把高频的几类整理在这里。第一是编码。Windows 控制台默认 GBK跑 tests 时如果用例数据里有中文控制台打印可能直接报 UnicodeEncodeError或者中文全变乱码。解决方案有两个层面日志输出到文件时指定encodingutf-8如果控制台也想要正确中文在运行前设置PYTHONIOENCODINGutf-8。但这个和环境相关不要写在代码里放到 CI 变量或者系统环境变量更合适。第二是路径。不要在用例里写open(data/xxx.json)这种相对路径。pytest 的当前工作目录不一定总是项目根目录CI 里的执行路径更不可控最好的方式是用Path(__file__).resolve()锚定到当前文件再按层级向上找项目根。我在 5.2 节已经给了示例这里再强调一遍因为这是很多新人从本地能跑搬到 CI 上就报错的主要原因。第三是 SSL 告警。被测环境经常用自签证书requests 请求时如果不加verifyFalse就会报 SSL 错误但加了之后又会输出InsecureRequestWarning。这个警告刷屏看着心烦可以在框架初始化时统一处理import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)这一行只应该出现在测试框架内部不要影响其他脚本。同时要注意自签证书的环境通常只存在于 dev 环境到 staging 或生产环境就不该再关掉证书校验了所以这个开关要跟环境配置联动而不是写死。第四是超时和用例执行时长。别忘了给套件设置整体超时或者在使用参数化时控制单个用例的执行时间否则一旦某个接口挂起整个回归会被拖死。可以在 pytest 里用 pytest-timeout 插件pytest-timeout2.2.0运行参数加--timeout60表示单个用例最长 60 秒超过就强制失败。对基础框架来说这比在代码里写一堆复杂的并发控制要实用得多。这套框架我从第一版写到今天中间重构过三次最深的体会是好框架不一定复杂但一定有一致的约定。请求都从 BaseApi 走断言都走统一函数用例数据都在同一套目录结构里做到这些哪怕核心代码只有几百行团队新同学一周内也能上手。也在这里给刚起步的同学一个建议别追求一步到位先把请求封装、断言、日志这三层落地用例超过三十条再上数据驱动有 CI 需求再补报告和流水线。框架是长出来的不是一次设计出来的。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询