pytest默认不处理死循环,因其仅为测试运行器而非沙箱,不监控或限制被测代码执行;需借助pytest-timeout插件显式配置超时机制。

pytest 本身不会自动检测或中断死循环,必须靠 pytest-timeout 插件显式配置超时机制,否则测试卡住就一直挂起。
为什么 pytest 默认不处理死循环
pytest 是测试运行器,不是沙箱环境。它不拦截、不监控、不限制被测代码的执行逻辑 —— 包括无限 while True、空 pass 循环、或阻塞 I/O 等。一旦测试函数内部陷入死循环,pytest 就只能等它自己结束(永远不会)。
常见现象:pytest test_slow.py 卡住不动,Ctrl+C 后看到堆栈停在你的测试函数里,而不是 pytest 框架层。
这不是 bug,是设计使然:pytest 假设你写的测试是良性的;防死循环属于“安全边界”需求,得靠插件补足。
安装并启用 pytest-timeout
先装插件:
pip install pytest-timeout
它会自动注册为 pytest 插件,无需额外导入。但必须显式启用超时策略,否则仍无效果。
有三种启用方式,按推荐顺序列出来:
- 命令行加
--timeout=30:对单次运行生效,比如pytest --timeout=30 test_deadloop.py - 在
pytest.ini或pyproject.toml中全局配置 timeout 值,避免每次敲参数 - 用
@pytest.mark.timeout(5)给特定测试加粒度更细的超时(注意:需确保该 mark 已被插件识别)
⚠️ 容易踩的坑:pytest-timeout 在 Windows 上默认用 threading.Timer,不可靠;Linux/macOS 默认用 signal.SIGALRM,更精准。跨平台项目建议统一加 --timeout-method=thread 参数规避差异。
超时后的行为和信号选择
超时触发时,pytest-timeout 会向测试线程发送终止信号。关键点在于信号类型影响退出干净程度:
-
--timeout-method=signal(默认 Linux/macOS):发SIGALRM,Python 解释器收到后抛pytest_timeout.TimeoutExpired异常,测试标记为 failed,能正常走teardown -
--timeout-method=thread:用子线程跑测试,主线程计时,超时后调threading.Thread._stop()(已弃用)或ctypes.pythonapi.PyThreadState_SetAsyncExc强制注入SystemExit—— 这种方式可能跳过finally和__exit__,资源泄漏风险高
所以,除非你明确在 Windows 下跑且无法改环境,否则别依赖 thread 方法。真要保兼容,不如在测试里主动加 try/finally 清理逻辑,而不是指望超时机制帮你兜底。
如何验证 timeout 是否生效
写一个故意死循环的测试来确认:
def test_infinite_loop():
import time
while True:
time.sleep(1) # 防止 CPU 占满,但仍是死循环
然后运行:pytest test_infinite_loop.py --timeout=2 --timeout-method=signal -v
你应该看到类似输出:
test_infinite_loop.py::test_infinite_loop FAILED ... pytest_timeout.TimeoutExpired: Test took longer than 2.0 seconds
如果没报这个错,说明插件没装、参数拼错(比如写成 --time-out)、或方法不匹配(Windows 用了 signal)。最稳的验证方式是看 pytest 输出里有没有出现 TimeoutExpired 字样 —— 这才是真正生效的标志。
复杂点在于:某些 C 扩展或底层阻塞调用(如 socket.recv() 无超时)可能绕过 Python 层信号,这时 thread 方法也救不了。这种场景得从源头加 timeout= 参数,而不是依赖 pytest-timeout。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











