pytest-timeout比signal.alarm更可靠,因其默认用threading.timer主动检查线程栈,可覆盖子线程、c扩展阻塞等signal无法触达的场景;而signal.alarm仅作用于主线程且受限于系统调用阻塞。

pytest-timeout 为什么比 signal.alarm 更可靠
在 Linux/macOS 上用 signal.alarm 给测试加超时,遇到子进程、多线程或某些 C 扩展(比如 requests 底层的 libcurl)时经常失效——信号只发给主线程,而阻塞可能发生在子线程或系统调用里。pytest-timeout 默认用 threading.Timer + 主动检查线程栈,能覆盖更多挂起场景;配合 --timeout-method=thread(默认)或更激进的 signal 模式(仅限 Unix),实际容错更强。
安装与全局超时设置的坑
直接 pip install pytest-timeout 即可,但注意两点:
- 不要在
conftest.py里用pytest_configure动态改timeout配置——它只影响后续收集的测试,已加载的 fixture 或参数化用例可能不生效 - 全局超时推荐写进
pyproject.toml,而不是命令行反复输:[tool.pytest.ini_options] timeout = 30 timeout_method = "thread"
否则 CI 脚本里漏传--timeout就失去防护 -
timeout单位是秒,不是毫秒;设成0.5看似合理,但 Python 的线程调度精度和 pytest 自身开销可能导致误杀,建议不低于 1 秒
@pytest.mark.timeout 只对当前函数生效
装饰器方式适合个别高风险用例,比如调用外部 HTTP 接口或执行 shell 命令:
@pytest.mark.timeout(5)
def test_external_api_call():
response = requests.get("https://slow.example.com/health", timeout=3)
assert response.status_code == 200
注意:
- 装饰器参数优先级高于全局配置,但不会继承
timeout_method,得显式写:@pytest.mark.timeout(5, method="thread") - 如果函数内部用了
async def,pytest-timeout默认不支持异步超时(会等整个 event loop 结束),得搭配pytest-asyncio并确保method="thread" - 被超时中断的测试,pytest 报告里显示为
failed,错误信息是TimeoutError: Test took longer than 5.0 seconds,不是KeyboardInterrupt或其他异常
超时后资源没清理干净怎么办
pytest-timeout 中断测试时,不会自动调用 teardown 或 yield fixture 的清理逻辑——这是最常被忽略的点。例如:
- 测试中启了本地临时服务器(
http.server.HTTPServer),超时后进程还在后台跑 - 用了
tempfile.mkdtemp()创建目录,但没进finally就被 kill,导致磁盘占满
解决方法只有两个:
- 所有关键资源必须用
try/finally包裹,别依赖 fixture 的 teardown 阶段 - 对进程类资源,启动时记下
pid,在finally里用os.kill(pid, signal.SIGTERM)强制收尾(Windows 用subprocess.Popen.terminate())
没有银弹。超时机制本身是“粗暴中断”,清理责任永远在测试代码侧。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











