在Python项目里用requests调用第三方接口时,超时和网络错误是必须考虑的情况。为了不让测试用例依赖外网,我们可以用responses库拦截requests发出的请求,在本地模拟Timeout与各类网络异常,从而稳定验证异常处理代码。
安装responses库
responses是requests的官方推荐mock工具,使用pip即可安装:
pip install responses
用responses.add模拟超时
responses提供了responses.add方法,可以指定异常类型。要模拟超时,传入exception=requests.exceptions.Timeout即可。下面示例展示如何用装饰器拦截请求并抛出超时:
import requests
import responses
@responses.activate
def test_timeout():
# 拦截对该URL的GET请求,并抛出Timeout异常
responses.add(
responses.GET,
"https://api.ipipp.com/ping",
exception=requests.exceptions.Timeout
)
try:
requests.get("https://api.ipipp.com/ping", timeout=1)
except requests.exceptions.Timeout as e:
print("捕获到超时异常:", e)
test_timeout()
模拟连接错误与其他网络异常
除了Timeout,还可以模拟连接失败、DNS解析错误等。只需更换exception类型:
import requests
import responses
@responses.activate
def test_connection_error():
responses.add(
responses.GET,
"https://api.ipipp.com/unreachable",
exception=requests.exceptions.ConnectionError
)
try:
requests.get("https://api.ipipp.com/unreachable", timeout=2)
except requests.exceptions.ConnectionError as e:
print("捕获到连接错误:", e)
test_connection_error()
在上下文管理器中批量注册
如果不想用装饰器,也可以用responses.RequestsMock()作为上下文管理器:
import requests
import responses
def run():
with responses.RequestsMock() as rsps:
rsps.add(
responses.POST,
"https://api.ipipp.com/upload",
exception=requests.exceptions.ReadTimeout
)
try:
requests.post("https://api.ipipp.com/upload", timeout=0.5)
except requests.exceptions.ReadTimeout:
print("读取超时逻辑生效")
run()
编写单元测试建议
在pytest中,建议将超时测试与正常响应测试分开,并使用参数化验证不同异常分支:
- 正常情况:返回状态码200和JSON数据
- Timeout:验证重试或降级逻辑
- ConnectionError:验证告警或兜底返回
| 异常类型 | requests异常类 | 常见触发场景 |
|---|---|---|
| 连接超时 | requests.exceptions.ConnectTimeout | 建连慢、网络不通 |
| 读取超时 | requests.exceptions.ReadTimeout | 服务端处理久 |
| 通用超时 | requests.exceptions.Timeout | 上述两者父类 |
| 连接错误 | requests.exceptions.ConnectionError | DNS或拒绝连接 |
小结
通过responses.add拦截请求并指定exception,我们可以在不依赖真实网络的前提下,精准模拟requests的Timeout与ConnectionError等异常,让超时处理逻辑的测试变得简单可靠。