pytest-timeout 是 pytest 的超时插件,通过系统信号或线程监控强制中断挂起测试,而非 try/except;它能处理 c 扩展、阻塞 i/o 等场景,但需注意平台差异、method 选择及资源清理问题。

pytest-timeout 是什么,为什么不能只靠 try/except
pytest-timeout 是 pytest 的一个插件,用于在测试函数或整个测试会话超时时主动中断执行。它不是靠 Python 层的 try/except 捕获,而是通过操作系统级信号(如 SIGALRM 在 Unix/Linux/macOS,或 Windows 的线程监控)强制终止挂起的测试线程。这意味着:即使测试卡死在 C 扩展、阻塞 I/O(如未响应的 socket.connect)、无限循环或死锁中,pytest-timeout 仍可能生效——而纯 Python 的 threading.Timer 或 signal.alarm 在这些场景下常失效。
安装与全局启用 timeout
先确保已安装:
pip install pytest-timeout
全局设置超时(推荐用于 CI 或防止所有测试失控):
- 在
pytest.ini、pyproject.toml或命令行中指定--timeout=30(单位:秒) -
pyproject.toml示例:[tool.pytest.ini_options] timeout = 30
- 注意:全局 timeout 对每个测试函数单独计时,不是整个测试套件总时长
- 若测试本身调用
time.sleep(10)且设了timeout=5,该测试会直接失败并报TimeoutError
@pytest.mark.timeout 覆盖单个测试
当某些测试天然耗时较长(如集成外部 API、加载大文件),但又不想放宽全局限制时,用装饰器精准控制:
import pytest <p>def test_slow_but_legit():</p><h1>正常逻辑</h1><pre class="brush:python;toolbar:false;">pass
@pytest.mark.timeout(60) # 覆盖全局 timeout,允许最多 60 秒 def test_external_api_call(): import requests requests.get("https://www.php.cn/link/13e0bbb1a265a0723712d18d25bb88b3") # 故意慢
关键点:
-
@pytest.mark.timeout可传method="signal"(默认,Unix 安全)或method="thread"(Windows 必选,但对 C 阻塞无效) - 若用
method="thread",超时后仅主线程抛出pytest_timeout.TimeoutError,被测线程可能仍在后台运行(资源泄漏风险) - 不建议在 fixture 中使用
@pytest.mark.timeout,它只作用于测试函数本身
常见失败现象和绕不过去的坑
你看到 TimeoutError 但测试没真正终止?或者 timeout 像没生效?大概率是以下情况:
- Windows 下未显式指定
method="thread":默认"signal"在 Windows 不可用,pytest 会静默忽略 timeout 设置 - 测试中用了
subprocess.run(..., timeout=...):这是 subprocess 自己的超时,和pytest-timeout无关;两者叠加可能导致行为混乱 - pytest 运行在容器里且禁用了
SIGALRM(如某些 Kubernetes 环境):此时必须切到method="thread",并接受其局限性 -
timeout值设得太小(比如0.1),而测试启动本身就有几十毫秒开销,导致误杀
最易被忽略的一点:timeout 是「硬中断」,不会等测试完成清理(如 teardown 或 __exit__)。如果测试打开了临时文件、数据库连接或网络 socket,它们可能残留——得靠外部机制(如 tmp_path 自动清理、pytest-xdist 的进程隔离)兜底。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











