做接口自动化测试这两年,我最大的感触是:95%的人都会用 Requests,但真正能把这个库用好的人,可能不到 5%。很多人用 Requests 的方式,永远停留在两行代码上:
resp = requests.post("https://xxx.com/api/login", json={"username": "admin", "password": "123456"}) print(resp.json())能跑通,但也就到此为止了。
一旦遇到登录态丢失、连接池报错、429 限流、SSL 证书校验失败、超时重试不会配、Session 和普通请求到底该用哪个……这些问题出现时,大多数人的做法是去搜索引擎复制一段代码,能解决就继续往下写,不能解决就换一个方案。至于底层到底发生了什么,始终是一团迷雾。
这篇文章我想换一种讲法:直接从 Requests 源码入手,把一条 HTTP 请求从发出到返回的完整链路拆开看,然后再落到接口自动化测试的真实场景,从登录、鉴权、业务接口到断言封装,一步步搭出一个可复用的工程。
先给一个明确判断:对于 Python 接口自动化测试来说,Requests 是所有工具链里最值得读源码的基础库。它不庞大,核心模块就那么几个,但它是整个测试框架的“地基”。把地基打牢,不管是写 pytest 测试用例、封装 APIClient,还是排查线上诡异问题,你都会比别人多一层认知优势。
1. 为什么接口自动化测试必须理解 Requests 源码
先不急着看代码,我们想清楚一个问题:接口自动化测试和普通脚本调用接口,本质区别是什么?
普通脚本调用接口,核心目标是“把请求发出去”,比如爬虫抓一个页面,或调试一个临时接口。它不关心状态管理,不关心请求链路,不关心可重复执行。
而接口自动化测试不一样。它要求:
- 测试用例可以重复执行,且每次执行结果稳定;
- 登录态可以被多个用例共享,而不是每个用例单独登录;
- 请求失败时能自动重试,而不是直接报错;
- 响应结果能被结构化成断言对象;
- 测试脚本要进入 CI/CD 流程,具备可维护性。
这就意味着,你不能只“会用” Requests,你必须理解它内部的机制,才能在它出现问题时快速定位。举几个真实场景:
场景一:登录态丢失
你写了一个 login 接口获取 token,然后下一个测试用例带着 token 去请求用户信息接口,结果发现 token 是拿到的,但请求发出去就是 401。排查半天,发现你每次都在用requests.get()而不是session.get(),token 虽然拼到了 header 里,但 Session 的 cookie 机制完全没被触发。
场景二:并发一高就报错
性能测试跑 50 个并发,Requests 频繁报Connection pool is full或者Max retries exceeded。你以为是服务器扛不住,实际是客户端连接池默认只有 10 个连接,你的并发量超过了连接池上限。
场景三:429 限流
接口返回 429,代码直接抛异常,任务中断。你需要在代码里配置重试和退避策略,但不知道重试逻辑是 Requests 做的还是 urllib3 做的,改了半天没效果。
这些问题的答案,全在 Requests 源码里。
所谓“手撕源码”,不是让你去背每一行代码,而是让你看懂调用链。当你脑海中有一条清晰的链路图——从requests.get()到最终 HTTP 请求发出,中间经历了哪些对象、哪些方法、哪些可配置点——你在使用 Requests 时就不再是“盲人摸象”。
2. Requests 核心架构:一条请求穿越的四个层级
Requests 库虽然使用起来非常简单,但它的内部是分层的。一条 HTTP 请求从发出到返回,核心路径会依次穿越四个层级:
| 层级 | 核心模块 | 职责 |
|---|---|---|
| API 层 | requests/api.py | 提供get()、post()等顶层函数,面向使用者 |
| Session 层 | requests/sessions.py | 管理 Cookie、Header、Hook、证书等会话状态 |
| Adapter 层 | requests/adapters.py | 负责具体传输,连接池管理、重试逻辑 |
| 底层库 | urllib3 | 真正的 HTTP 连接建立、请求发送、响应读取 |
很多人不知道,** Requests 并不是自己在底层发 HTTP 请求的,它真正依赖的是 urllib3。** Requests 做的事情,是把 URL、参数、Header、Cookie、超时、证书、代理这些复杂配置统一封装成一套友好 API,然后交给 urllib3 去执行。
换句话说:
- Requests 是“大脑”:负责组装请求、管理状态、处理策略。
- urllib3 是“手脚”:负责真正把数据包发出去,并接收响应。
理解这一点非常重要。因为很多参数——比如重试、连接池大小——其实是透传给 urllib3 的。你只在 Requests 层找配置,永远找不到。
让我们从最常用的一行代码开始,看看它到底经历了什么:
requests.get("https://api.example.com/users")这行代码的调用链是这样的:
requests.api.get() -> request("get", url) -> Session().request("get", url) -> Request() 对象构建 -> Session.prepare_request() -> PreparedRequest 对象 -> Session.send() -> HTTPAdapter.send() -> urllib3 PoolManager 连接池 -> HTTPConnectionPool.urlopen() -> HTTP 请求真正发出可以看到,你表面上只调用了一个函数,实际上内部经历了近 10 个步骤。接口自动化测试真正要深入掌握的,就是这个链条中 Session 层和 Adapter 层的机制。
3. 从源码理解 Session 与连接复用机制
在接口自动化测试中,最常见的错误之一是每个测试用例都直接调用requests.get()或requests.post(),导致登录态丢失、连接无法复用。
先看requests/api.py的源码实现。虽然具体版本的源码行数有差异,但核心逻辑是非常稳定的:
# requests/api.py(逻辑简化版,便于理解调用链) def request(method, url, **kwargs): with sessions.Session() as session: return session.request(method=method, url=url, **kwargs) def get(url, params=None, **kwargs): return request("get", url, params=params, **kwargs) def post(url, data=None, json=None, **kwargs): return request("post", url, data=data, json=json, **kwargs)看到问题了吗?
每次调用requests.get()或requests.post(),内部都会临时创建了一个新的 Session,并在请求完成后立即关闭。这意味着:
- Cookie 无法自动保存。上一次响应中
Set-Cookie设置的内容,不会带到下一次请求中; - 连接无法复用。每个请求都要重新建立 TCP 连接,甚至重新做 TLS 握手,性能非常差;
- Header 无法统一管理。你必须在每个请求里重复传同一个 Header。
这就是为什么接口自动化测试中,几乎都会使用requests.Session()来发起请求。
我们再看sessions.py中Session.request()的核心逻辑:
# requests/sessions.py(逻辑简化版) class Session(SessionRedirectMixin): def __init__(self): self.headers = default_headers() self.cookies = cookiejar_from_dict({}) self.auth = None self.proxies = {} self.verify = True self.cert = None self.max_redirects = DEFAULT_REDIRECT_LIMIT self.adapters = OrderedDict() # 默认为 http 和 https 注册 HTTPAdapter self.mount("https://", HTTPAdapter()) self.mount("http://", HTTPAdapter()) def request(self, method, url, params=None, data=None, headers=None, cookies=None, files=None, auth=None, timeout=None, allow_redirects=True, proxies=None, hooks=None, stream=None, verify=None, cert=None, json=None): # 1. 构造 Request 对象 req = Request( method=method.upper(), url=url, headers=headers, files=files, data=data or {}, json=json, params=params or {}, auth=auth, cookies=cookies, hooks=hooks, ) # 2. 生成 PreparedRequest prep = self.prepare_request(req) # 3. 合并发送参数 send_kwargs = { "timeout": timeout, "allow_redirects": allow_redirects, "proxies": proxies, "stream": stream, "verify": verify, "cert": cert, } # 4. 发送请求 resp = self.send(prep, **send_kwargs) return resp关键信息有三个:
3.1 所有状态都挂在 Session 上
Session对象初始化时会创建:
headers:统一的默认请求头;cookies:可自动保存的 CookieJar;auth:默认认证信息;adapters:不同协议对应的传输适配器。
也就是说,Session 是一个“状态容器”。你在一个 Session 上发出的所有请求,都会共享这份状态。
3.2 Cookie 自动保存的机制
在Session.send()中,Requests 会在收到响应后自动把Set-Cookie写入self.cookies。你下一次通过同一个 Session 发请求时,prepare_request()会把保存在 Session 里的 cookies 合并到新请求中。
这就是为什么登录接口用session.post()执行一次后,后续所有带鉴权的请求都能自动带上 Cookie。
3.3 Adapter 和连接池复用
再看Session.send()的核心部分:
def send(self, request, **kwargs): # ... adapter = self.get_adapter(url=request.url) r = adapter.send(request, **kwargs) return rSession如何找到对应的 Adapter?就是通过初始化时mount()的 URL 前缀。默认注册了两个:
http://对应HTTPAdapter()https://对应HTTPAdapter()
HTTPAdapter内部维护了一个urllib3.PoolManager连接池。同一个 host 的请求,会复用同一个连接池中的 TCP 连接,这就是 Session 比直接多次调用requests.get()高效的原因。
到这里,核心结论已经很清楚:
接口自动化测试中,应该用
requests.Session()封装客户端,而不是散落地调用requests.get()/requests.post()。
4. 环境准备与最小工程结构
下面进入实战环节。我们先准备一个可复用的接口自动化测试最小工程。
4.1 环境依赖
建议使用 Python 3.9 及以上版本。本文的代码不依赖某个特定版本的 Requests,只要你的环境是 Requests 2.x 系列即可。
创建虚拟环境并安装依赖:
python -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate pip install requests pytest pytest-html安装完成后,验证一下:
python -c "import requests; print(requests.__version__)"如果能输出版本号,说明环境就绪。
4.2 工程目录结构
我建议按下面的结构组织测试工程:
api_test_project/ ├── common/ │ ├── __init__.py │ └── http_client.py # APIClient 封装 ├── test_cases/ │ ├── __init__.py │ ├── test_login.py # 登录用例 │ └── test_user.py # 业务接口用例 ├── config/ │ ├── __init__.py │ └── settings.py # 环境配置 ├── reports/ # 测试报告输出目录 └── pytest.inipytest.ini文件内容:
[pytest] testpaths = test_cases addopts = -v --tb=short这个结构虽然简单,但足够支撑一个中大型接口测试项目的基本骨架。
5. 接口自动化测试完整实战:从登录态到业务链路
假设我们正在测试一个典型的 RESTful 接口服务,环境地址为http://127.0.0.1:8000。它提供两个关键接口:
POST /api/login:登录接口,入参为username、password,成功返回{"code": 0, "data": {"token": "xxxx"}};GET /api/user/info:获取用户信息,需要请求头携带Authorization: Bearer <token>。
很多测试项目会遇到的问题是:用户信息接口依赖登录接口返回的 token,但登录接口又只在第一个用例中执行一次。如果每次请求都重新登录,测试效率很低;如果登录态不共享,后面的用例又全部失败。
用 Session 封装可以优雅地解决这个问题。
5.1 封装 APIClient
新建common/http_client.py:
# common/http_client.py import requests class APIClient: """接口测试客户端,基于 requests.Session 封装""" def __init__(self, base_url: str): self.base_url = base_url.rstrip("/") self.session = requests.Session() self.token = None def login(self, username: str, password: str) -> dict: """登录接口:获取 token 并自动写入 Session 的 Header""" url = f"{self.base_url}/api/login" resp = self.session.post( url, json={"username": username, "password": password}, ) # 如果服务端返回非 2xx,直接抛出异常,避免用例继续往下跑 resp.raise_for_status() data = resp.json() if data.get("code") != 0: raise AssertionError(f"登录失败: {data}") self.token = data["data"]["token"] # 将 token 写入统一 Header,后续所有请求自动携带 self.session.headers.update({"Authorization": f"Bearer {self.token}"}) return data def get_user_info(self) -> dict: """获取当前登录用户信息""" url = f"{self.base_url}/api/user/info" resp = self.session.get(url) resp.raise_for_status() return resp.json()这段代码的关键设计是:
- 所有请求都通过
self.session发出,而不是requests.get(),保证 Cookie 和连接池的复用。 - 登录成功后,统一更新 Session 的 Header,后续业务接口不用再手动拼 token。
- 对响应状态码做快速失败,避免接口异常时用例继续执行造成误判。
5.2 编写 pytest 测试用例
新建test_cases/test_user.py:
# test_cases/test_user.py import pytest from common.http_client import APIClient @pytest.fixture(scope="module") def client(): """模块级 fixture:整个测试模块只初始化一次客户端""" api_client = APIClient(base_url="http://127.0.0.1:8000") return api_client @pytest.fixture(scope="module") def login(client): """模块级 fixture:只执行一次登录,供所有用例复用登录态""" client.login("admin", "123456") return client def test_login_success(client, login): """登录后不应为 None,同时 token 应该被写入 Header""" assert client.token is not None assert client.session.headers.get("Authorization") == f"Bearer {client.token}" def test_get_user_info_authorized(login): """携带 token 请求用户信息,应返回 200 和正确用户名""" data = login.get_user_info() assert data["code"] == 0 assert data["data"]["username"] == "admin" def test_get_user_info_unauthorized(): """不登录直接请求用户信息,应被拒绝""" api_client = APIClient(base_url="http://127.0.0.1:8000") resp = api_client.session.get(f"{api_client.base_url}/api/user/info") assert resp.status_code == 401这里有个很典型的测试设计模式:“先失败、后成功”。
第三个用例故意不登录,模拟一个未认证请求,验证服务端正确处理返回 401。很多新手只写“成功路径”的用例,导致服务端鉴权失效这种严重问题完全暴露不出来。
5.3 运行测试并查看结果
pytest预期输出中包含类似信息:
test_cases/test_user.py::test_login_success PASSED test_cases/test_user.py::test_get_user_info_authorized PASSED test_cases/test_user.py::test_get_user_info_unauthorized PASSED如果登录失败或 token 未生效,第一个用例就会失败,后续用例也会连带失败。这种“快速失败 + 状态共享”的设计,能帮助你在接口测试中快速定位是登录问题还是业务逻辑问题。
6. 深入源码:Request 是如何变成 PreparedRequest 的
前面的实战代码能跑通,但很多人不理解一个问题:为什么我传入的json会被自动编码为请求体?为什么 URL 参数会自动拼接?
答案在PreparedRequest中。
在Session.request()中,Request对象会被传给prepare_request()方法。Request本质上只是一个“数据容器”,它保存了你传入的参数,但还没有变成可以发送的 HTTP 请求。
prepare_request()的核心逻辑如下:
def prepare_request(self, request): # 如果请求没有单独指定 cookies,则使用 Session 里保存的 cookies cookies = request.cookies or {} if not cookies: cookies = self.cookies # 创建 PreparedRequest,并执行 prepare 系列方法 prep = PreparedRequest() prep.prepare( method=request.method, url=request.url, headers=request.headers, files=request.files, data=request.data, json=request.json, params=request.params, auth=request.auth, cookies=cookies, hooks=request.hooks, ) return prep而PreparedRequest.prepare()内部会按顺序调用多个prepare_xxx方法,主要步骤包括:
| 方法 | 职责 |
|---|---|
prepare_method | 将请求方法转为大写,如get→GET |
prepare_url | 解析和校验 URL,处理 URL 中的非 ASCII 字符 |
prepare_headers | 合并默认 Header 和用户传入 Header |
prepare_body | 根据data、json、files生成请求体并设置 Content-Type |
prepare_auth | 处理 Basic Auth、Bearer Token 等认证信息 |
prepare_cookies | 将 Cookie 写入请求头 |
prepare_hooks | 注册钩子函数 |
举个例子。你调用:
session.post("http://127.0.0.1:8000/api/login", json={"username": "admin"})在prepare_body()阶段,Requests 会检查到json参数不为空,于是:
- 调用
complexjson.dumps()把字典序列化成 JSON 字符串; - 设置
Content-Type: application/json请求头; - 把序列化后的字符串写入请求体。
这也是为什么很多新手分不清data和json的区别:
data传字符串或者字典,Requests 会把它当作表单格式进行编码;json传字典,Requests 会自动序列化为 JSON 字符串,并设置Content-Type: application/json。
接口测试中,到底用data还是json,取决于后端接口到底接收 form 格式还是 JSON 格式。如果你把 JSON 数据用data传过去,后端用@RequestBody接,就会直接报参数缺失。
反过来,表单类型的接口你用json传,后端用@RequestParam或者request.form接,同样拿不到数据。
7. 接口测试高频问题与排查思路
接口测试做得越多,你遇到的神奇问题就越多。这里把 Requests 使用中最容易踩的坑整理成了一张排查表:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 接口返回 401 / 403 | 登录态未保存或 token 未传 | 打印发起请求的 headers 和 cookies | 改用 Session,登录后将 token 写入统一 Header |
| 429 Too Many Requests | 超过了服务端限流阈值 | 查看响应头的Retry-After字段 | 配置重试策略,增加退避时间 |
Max retries exceeded | URL 写错、服务端拒绝连接、网络不通 | 用curl手动请求,确认接口可达 | 检查 URL、代理设置、DNS;确认服务是否启动 |
| SSL 证书校验失败 | 测试环境使用自签名证书 | 查看完整报错信息 | 测试环境可临时设置verify=False,生产环境不建议 |
| 响应中文乱码 | 请求头或响应头缺少 charset | 打印resp.encoding | 手动指定resp.encoding = "utf-8" |
| 连接池报错 | 并发量超过连接池默认上限 | 查看异常栈是否包含urllib3.connectionpool | 调大HTTPAdapter的pool_maxsize |
| 脚本执行时间过长 | 未设置 timeout,请求挂起 | 检查是否有请求长时间未返回 | 所有请求都设置timeout |
7.1 重点:429 限流的正确重试姿势
接口测试中,429 是非常常见的状态码,尤其是被测服务没有做好限流或者你跑的用例太密集时。
很多人遇到 429 的第一反应是“等一会儿再跑”,但人工等待不适用于自动化测试。正确做法是在客户端配置重试策略。
注意,重试逻辑不在 Requests 层,而是在 urllib3 层,通过HTTPAdapter传入:
# common/http_client.py(重试策略增强版) import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry def create_session_with_retry( retry_times: int = 3, backoff_factor: float = 1.0, status_forcelist: tuple = (429, 500, 502, 503, 504), ) -> requests.Session: """ 创建带重试策略的 Session backoff_factor=1.0 表示第一次重试等待 1 秒,第二次 2 秒,第三次 4 秒 """ retry_strategy = Retry( total=retry_times, backoff_factor=backoff_factor, status_forcelist=status_forcelist, # 不同 urllib3 版本参数名可能不同:allowed_methods / method_whitelist allowed_methods=["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"], raise_on_status=False, ) adapter = HTTPAdapter(max_retries=retry_strategy) session = requests.Session() session.mount("http://", adapter) session.mount("https://", adapter) return sessionbackoff_factor的机制值得解释一下:它是 urllib3 的重试退避因子。计算公式是:
sleep_time = backoff_factor * (2 ** (重试次数 - 1))也就是backoff_factor=1时,第一次重试前等待 1 秒,第二次等待 2 秒,第三次等待 4 秒。这种指数退避策略可以显著降低对服务端的瞬时压力,比固定间隔重试更可靠。
不过要注意:不是所有接口都适合自动重试。只有幂等接口——比如查询类接口——才能放心重试。如果是一个创建订单的 POST 接口,重试可能导致订单重复创建。稳妥的做法是只在 GET 请求上配置这种全局重试,写操作接口单独处理。
7.2 重点:连接池扩容
Requests 默认连接池很小,HTTPAdapter的默认参数是pool_connections=10、pool_maxsize=10。
如果是做并发测试,或者测试脚本是多线程发请求的,很容易出现连接池不够用的情况。可以通过调整 Adapter 参数解决:
adapter = HTTPAdapter( pool_connections=20, pool_maxsize=50, max_retries=retry_strategy, ) session.mount("https://", adapter)pool_connections表示缓存的连接池数量,pool_maxsize表示每个连接池最大连接数。具体数值要根据你测试的并发规模来定,但这几个参数的含义必须清楚。
8. 接口自动化测试最佳实践:工程化落地建议
最后一章,聊一些接口自动化测试工程化的建议。这些都是从实际项目中总结出来的,不会写进官方文档里。
8.1 永远封装 APIClient,不要散写请求
最差的接口测试代码,是每个用例里都写几十行requests.post(),参数、Header、断言全部挤在一起。这种代码维护成本极高,接口一变更,所有用例都要改。
正确做法是把接口请求封装到一个APIClient类里,每个接口对应一个方法。用例层面只关心“入参”和“期望结果”,不关心 HTTP 细节。
8.2 环境配置和敏感信息要分离
不要把测试环境的地址、账号密码直接硬编码在测试代码里。至少做成配置文件:
# config/settings.py import os BASE_URL = os.getenv("BASE_URL", "http://127.0.0.1:8000") USERNAME = os.getenv("TEST_USERNAME", "admin") PASSWORD = os.getenv("TEST_PASSWORD", "123456")生产环境的凭据信息,应该通过 CI 的 Secret 变量注入,而不是提交到代码仓库。
8.3 严格区分测试环境与生产环境的安全边界
接口测试一般针对测试环境或预发布环境。如果是在生产环境做只读验证,必须使用最小权限账号,并且只允许执行 GET 等只读请求。任何涉及删除、批量修改、资金操作的接口,都不应该在无人审批的情况下自动执行。
8.4 断言要结构化,不要只判断状态码
新手写断言最喜欢写assert resp.status_code == 200,但状态码 200 只代表 HTTP 请求成功,不能代表业务成功。很多系统在业务异常时,也会返回 HTTP 200,只是业务码变了。
更稳的断言思路是:
- 先判断 HTTP 状态码;
- 再判断业务码(如
code == 0); - 再校验关键业务字段(如
token不为空、username正确); - 必要的时候校验响应时间,防止接口性能劣化。
8.5 测试数据要做到可重复执行
接口测试最怕“反复执行结果不一致”。所以测试数据设计非常重要:
- 登录账号尽量用专用测试账号,不要和其他环境共用;
- 写入类操作尽量使用随机数据或唯一标识,避免数据冲突;
- 测试结束后做数据清理,避免垃圾数据累积。
你可以用 UUID 或者时间戳生成唯一用户名:
import time username = f"test_{int(time.time())}_{uuid.uuid4().hex[:8]}"8.6 善用 pytest fixture 管理资源
上一章实战代码里的 fixture 就是很好的示范:
clientfixture 负责初始化 APIClient;loginfixture 负责完成登录;- 不同用例可以灵活声明依赖哪个 fixture,做到登录态复用和用例隔离的平衡。
如果某个用例确实需要独立的登录态,就单独创建一个 fixture,而不要污染全局的登录 fixture。
8.7 日志和报告是排查问题的第一抓手
接口自动化测试一旦失败,最重要的不是看断言代码,而是看当时的请求报文和响应报文。所以,封装 APIClient 时可以考虑加日志输出:
import logging logger = logging.getLogger("api_test") def _request_with_log(self, method, url, **kwargs): logger.info(f"请求: {method} {url}") logger.info(f"请求参数: {kwargs}") resp = self.session.request(method, url, **kwargs) logger.info(f"响应状态码: {resp.status_code}") logger.info(f"响应内容: {resp.text[:500]}") return resp有了请求日志和响应日志,排查问题就能少花一半时间。
8.8 指定 timeout,永远不要让请求无限挂起
接口测试里最可怕的不是“很快失败”,而是“一直不返回”。一个没有设置 timeout 的请求,可能让整个测试任务挂在那里,白白占用 CI 资源。
resp = self.session.post(url, json=data, timeout=10)这里的timeout=10表示连接超时和读取超时都是 10 秒。你甚至可以单独设置:
timeout=(3.05, 15)第一个值是连接超时,第二个值是读取超时。每次请求都显式设置 timeout,应该成为接口测试的强制规范。
9. 总结与下一步学习方向
这篇文章从 Requests 源码的调用链讲起,落到接口自动化测试的真实工程实践。核心信息可以浓缩成四句话:
- Requests 本质是状态管理和请求装配层,真正发请求的是底层的 urllib3;
- 接口自动化测试要用 Session,不要散落调用 requests.get() / post(),这是登录态和连接池正确复用的基础;
- 重试策略、连接池大小都在 Adapter 层配置,在 Requests 顶层找不到答案;
- 接口测试工程的稳定性,靠的是封装、断言、日志、数据隔离和 timeout 规范,而不是某一行魔法代码。
读完这篇文章,建议你接下来做三件事:
第一,打开你本地的 Requests 安装目录,找到requests/sessions.py和requests/adapters.py,顺着文章里的调用链把Session.request()、Session.send()、HTTPAdapter.send()这三个方法完整读一遍。
第二,把文章里的APIClient封装落地到你自己的测试项目里,加上日志和 timeout 规范。
第三,深入读一读urllib3.util.retry.Retry的源码,理解指数退避、重试状态码、重试方法这些参数的完整含义。
Requests 是一个很适合“手撕源码”的库,因为它足够小、足够经典、足够常用。把这一层吃透,你的接口自动化测试能力会上一个台阶,也为你后续去理解 pytest 框架、HTTPX 库、以及各类自动化测试平台打下基础。
建议收藏备用,下次在接口测试里遇到诡异问题,不妨回来翻翻这篇文章的调用链图和排查表。