pytest-timeout - 失败测试而不是杀死整个测试运行

时间:2017-10-16 09:22:10

标签: python testing timeout pytest

我知道在pytest-timeout我可以为每个测试用例指定tiemout,但是单个失败会终止整个测试运行,而不会导致测试用例失败。

我是否被迫自行解决这个问题,或者有现成的工具可以提供这个解决方案吗?

2 个答案:

答案 0 :(得分:2)

我很久以前就研究过这个问题,并且得出的结论是自制的解决方案会更好。

我的插件正在杀死整个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& amp; 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 =======

答案 1 :(得分:0)

pytest-timeout从一开始就完全支持此方法,您想使用pytest-timeout的自述文件中所述的signal方法。请务必仔细阅读自述文件,因为其中包含一些警告。确实,它是使用SIGALRM实现的,如另一个答案所示,但是它已经存在,因此无需重新执行此操作。