pytest-rerunfailures是一个专为应对偶发性失败(flaky test)设计的pytest插件,适用于网络抖动、第三方服务超时、资源竞争等外部环境导致的临时性失败,但对逻辑错误、断言缺陷等必然失败无效。

pytest-rerunfailures 是什么,它能解决哪类问题?
pytest-rerunfailures 不是修复测试代码的工具,它只是在测试失败时自动重跑——适用于那些因外部依赖(如网络抖动、数据库连接超时、临时资源竞争)导致的偶发失败(flaky test)。它对逻辑错误、断言写错、环境配置缺失这类必然失败毫无作用。如果你的测试每次运行都失败,加 --reruns 3 只会让 CI 耗时更长、日志更混乱。
典型适用场景包括:
- 调用第三方 HTTP API 的测试(
requests.get偶然超时) - 使用
time.sleep()等待异步任务完成的测试(竞态未完全覆盖) - 共享测试数据库被其他并发测试干扰(如未隔离事务或未清库)
如何正确安装并启用重试机制?
先确保已安装:pip install pytest-rerunfailures
最简启用方式是命令行加参数:pytest --reruns 2 --reruns-delay 1
-
--reruns 2表示最多重试 2 次(即总共运行 3 次) -
--reruns-delay 1表示每次重试前等待 1 秒(避免连续猛冲接口)
也可以写进 pyproject.toml 避免每次敲命令:
[tool.pytest.ini_options] reruns = 2 reruns_delay = 1
⚠️ 注意:--reruns 默认只对「失败(failure)」和「错误(error)」重试,不重试跳过(skip)或 xfail。如果想让某些测试明确支持重试(比如标记为 flaky),可以加装饰器:
@pytest.mark.flaky(reruns=3, reruns_delay=2)
重试时容易忽略的副作用和陷阱
重试会改变测试的执行上下文,几个关键点必须手动处理:
-
setup_method/setup_function会在每次重试时重新执行,但如果你在其中创建了全局状态(如写文件、改环境变量、启动临时服务),没清理就会污染后续重试; - 数据库 fixture 若没做事务回滚或清表,第二次重试可能因数据已存在而报错;
- 日志/截图类操作若没按重试次数区分命名,会相互覆盖,导致排查时找不到第一次失败的现场;
- CI 环境中,重试可能掩盖真实稳定性问题——比如某次重试成功了,但没人去查为什么第一次失败。
建议在重试测试里显式控制副作用:
def test_api_timeout():
for attempt in range(3):
try:
resp = requests.get("https://api.example.com/health", timeout=2)
assert resp.status_code == 200
break
except (requests.Timeout, AssertionError):
if attempt == 2:
raise
time.sleep(1)
怎么判断一个失败是否真的适合用 rerunfailures?
看失败是否具备以下特征:
- 失败不可复现:本地连跑 10 次只有 1–2 次失败,且失败位置随机(有时在 A 断言,有时在 B);
- 错误信息含典型瞬态关键词:
ConnectionError、TimeoutError、ResourceWarning、concurrent.futures.TimeoutError; - 失败只出现在 CI,不在本地稳定环境中;
- 同一测试在不同 worker 上失败率不一致(说明和资源调度有关)。
如果失败总是出现在同一行、抛出同一异常(如 KeyError: 'user_id')、或每次失败堆栈完全一致,那不是偶发问题,是 bug,该修代码,不该加重试。
重试是止痛药,不是抗生素。加得太多,只会让真正脆弱的测试越来越难被发现。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











