我知道在
我是否被迫自己解决这个问题,或者有现成的工具可以提供这个功能?
pytest-timeout
中,我可以为每个测试用例指定超时时间,但是单个失败会终止整个测试运行而不是只是失败的测试用例。我是否被迫自己解决这个问题,或者有现成的工具可以提供这个功能?
我很久以前就研究过这个问题,也得出结论,自制的解决方案会更好。
我的插件会终止整个pytest进程,但可以轻松调整为仅使单个(当前)测试失败。以下是调整后的草稿:
import pytest
import signal
class Termination(SystemExit):
pass
class TimeoutExit(BaseException):
pass
def _terminate(signum, frame):
raise Termination("Runner is terminated from outside.")
def _timeout(signum, frame):
raise TimeoutExit("Runner timeout is reached, runner is terminating.")
@pytest.hookimpl
def pytest_addoption(parser):
parser.addoption(
'--timeout', action='store', dest='timeout', type=int, default=None,
help="number of seconds before each test failure")
@pytest.hookimpl
def pytest_configure(config):
# Install the signal handlers that we want to process.
signal.signal(signal.SIGTERM, _terminate)
signal.signal(signal.SIGALRM, _timeout)
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item, nextitem):
# Set the per-test timeout (an alarm signal).
if item.config.option.timeout is not None:
signal.alarm(item.config.option.timeout)
try:
# Run the setup, test body, and teardown stages.
yield
finally:
# Disable the alarm when the test passes or fails.
# I.e. when we get into the framework's body.
signal.alarm(0)
kill -ALRM $pid
或每个测试因预设的闹钟超时而单独失败时,只有当前测试会失败,但其他测试将继续执行。TimeoutExit
不会被执行except Exception: pass
的库所抑制,因为它继承自BaseException
。SystemExit
。但是,与SystemExit
或KeyboardInterruption
不同,pytest不会捕获它,并且不会在此类异常上退出。time.sleep(...)
等任何信号),异常都将注入其中。pytest-timeout
不兼容,因为它也使用ALRM信号来实现相同的目的。kill -TERM $pid
或简单的kill $pid
(优雅的终止)情况下,由于它继承自SystemExit
,因此将立即终止-这通常不会被代码或pytest捕获。conftest.py
文件中(伪插件)。import time
def test_this():
try:
time.sleep(10)
except Exception:
pass
def test_that():
pass
在没有设置超时的情况下运行pytest不会有任何效果,两个测试都会通过:
$ pytest -s -v
.........
collected 2 items
test_me.py::test_this PASSED
test_me.py::test_that PASSED
======= 2 passed in 10.02 seconds =======
使用超时运行它会导致第一个测试失败,但通过第二个测试:
$ pytest -s -v --timeout=5
.........
collected 2 items
test_me.py::test_this FAILED
test_me.py::test_that PASSED
============== FAILURES ==============
______________ test_this _____________
def test_this():
try:
> time.sleep(10)
test_me.py:5:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
signum = 14, frame = <frame object at 0x106b3c428>
def _timeout(signum, frame):
> raise TimeoutExit("Runner timeout is reached, runner is terminating.")
E conftest.pytest_configure.<locals>.TimeoutExit: Runner timeout is reached, runner is terminating.
conftest.py:24: TimeoutExit
======= 1 failed, 1 passed in 5.11 seconds =======
signal
方法。请仔细阅读 readme,因为它带有一些注意事项。实际上,它是使用 SIGALRM 实现的,正如其他答案所建议的那样,但它已经存在,所以不需要重新实现。