pytest超时-失败测试反而会扼杀整个测试运行



我知道在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相似。但是,与SystemExitKeyboardInterruption不同,pytest不会捕获它,并且不会在出现此类异常时退出。

异常将被注入到警报时刻测试所做的任何地方,即使它是time.sleep(...)(对于任何信号也是如此)。

请记住,您只能为进程设置一个单独的警报(操作系统限制)。这也使得它与pytest-timeout不兼容,因为它也将ALRM信号用于相同的目的。

如果你想拥有全球性的&每次测试超时,您都必须实现智能警报管理器,它将跟踪少数警报,将操作系统警报设置为最早的警报,并决定在收到警报信号时调用哪个处理程序。


如果您执行kill -TERM $pid或仅执行kill $pid(优雅终止),它将立即终止——因为它继承自SystemExit,即BaseException,通常不会被代码或pytest捕获。

后一种情况主要展示了如何对不同的信号设置不同的反应。你可以用USR1&USR2和其他可捕获信号。


为了进行快速测试,请将上面的插件代码放在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 =======

pytest timeout从一开始就完全支持这一点,您希望使用pytest timeoutreadme中描述的signal方法。请仔细阅读自述,因为它附带了一些注意事项。事实上,正如另一个答案所暗示的那样,它是使用SIGALRM实现的,但它已经存在,因此无需重新执行。

相关内容

  • 没有找到相关文章

最新更新