如何使用pytest检查是否未引发Error

par*_*let 50 python raise pytest

让我们假设我们有这样的结果:

import py, pytest

ERROR1 = ' --- Error : value < 5! ---'
ERROR2 = ' --- Error : value > 10! ---'

class MyError(Exception):
    def __init__(self, m):
        self.m = m

    def __str__(self):
        return self.m

def foo(i):
    if i < 5:
        raise MyError(ERROR1)
    elif i > 10:
        raise MyError(ERROR2)
    return i


# ---------------------- TESTS -------------------------
def test_foo1():
    with pytest.raises(MyError) as e:
        foo(3)
    assert ERROR1 in str(e)

def test_foo2():
    with pytest.raises(MyError) as e:
        foo(11)
    assert ERROR2 in str(e)

def test_foo3():
        ....
        foo(7)
         ....
Run Code Online (Sandbox Code Playgroud)

问:我如何让test_foo3()进行测试,没有引发MyError?很明显,我可以测试一下:

def test_foo3():
    assert foo(7) == 7
Run Code Online (Sandbox Code Playgroud)

但我想通过pytest.raises()测试.有可能吗?例如:在一个案例中,该函数"foo"根本没有返回值,

def foo(i):
    if i < 5:
        raise MyError(ERROR1)
    elif i > 10:
        raise MyError(ERROR2)
Run Code Online (Sandbox Code Playgroud)

imho,用这种方式测试是有意义的.

Far*_*hin 92

如果测试引发任何意外的异常,测试将失败.你可以调用foo(7),你将测试不会引发MyError.所以,以下就足够了:

def test_foo3():
    foo(7)
Run Code Online (Sandbox Code Playgroud)

如果你想明确并为此编写一个断言语句,你可以这样做:

def test_foo3():
    try:
        foo(7)
    except MyError:
        pytest.fail("Unexpected MyError ..")
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,它有效,但它似乎更像是一个黑客,而不是一个干净的解决方案。例如,对 foo(4) 的测试将失败,但不是由于断言错误。 (4认同)
  • @paraklet - pytest的主要标语是["无样板测试"](http://pytest.readthedocs.org/en/latest/).这是pytest的精神,让你能够像Faruk的第一个例子一样编写测试,而pytest会为你处理细节.对我来说,第一个例子是"清洁解决方案",第二个例子似乎是不必要的冗长. (4认同)
  • foo(4) 的测试将失败,因为它将抛出意外的异常。另一种方法是将其包装在 try catch 块中,并失败并显示特定消息。我会更新我的答案。 (3认同)
  • 我喜欢代码具有可读性。如果我看到“pytest.notRaises()”,我清楚地看到测试的目的是检查是否未引发异常。如果我只是执行代码并且没有断言,我的第一个想法将是“这里缺少一些东西......”。是的,我可以为此写一条评论,但我更喜欢代码不言自明,而不是评论。 (3认同)

Pit*_*kos 12

建立在Oisin提到的基础之上..

你可以创建一个not_raises类似于pytest的简单函数raises:

from contextlib import contextmanager

@contextmanager
def not_raises(exception):
  try:
    yield
  except exception:
    raise pytest.fail("DID RAISE {0}".format(exception))
Run Code Online (Sandbox Code Playgroud)

如果你想坚持raises拥有一个对应物,那么你的测试更具可读性,这很好.从本质上讲,除了运行你想要在自己的行上测试的代码块之外,你真的不需要任何东西 - 一旦该块引发错误,pytest就会失败.

  • 我希望这被内置到 py.test 中;在某些情况下,它会使测试更具可读性。特别是与`@pytest.mark.parametrize` 结合使用。 (3认同)

Nik*_*ikT 12

由于这个问题得到了回答,pytest 文档已经更新了关于这个主题的信息,这里值得一提。

https://docs.pytest.org/en/6.2.x/example/parametrize.html#parametrizing-conditional-rising

它与其他一些答案类似,但使用parametrize较新的内置函数nullcontext使解决方案非常干净。

一个潜在的Python3.7 +例子如下所示:

from contextlib import nullcontext as does_not_raise
import pytest


@pytest.mark.parametrize(
    "example_input,expectation",
    [
        (3, does_not_raise()),
        (2, does_not_raise()),
        (1, does_not_raise()),
        (0, pytest.raises(ZeroDivisionError)),
    ],
)
def test_division(example_input, expectation):
    """Test how much I know division."""
    with expectation:
        assert (6 / example_input) is not None
Run Code Online (Sandbox Code Playgroud)

使用parametrize这种方式可以组合 OP 的测试用例,例如:

@pytest.mark.parametrize(
    "example_input,expectation,message",
    [
        (3, pytest.raises(MyError), ERROR1),
        (11, pytest.raises(MyError), ERROR2),
        (7, does_not_raise(), None),
    ],
)
def test_foo(example_input, expectation, message):
    with expectation as e:
        foo(example_input)
    assert message is None or message in str(e)
Run Code Online (Sandbox Code Playgroud)

这样做可以让您测试它没有引发任何异常nullcontext是作为可选上下文管理器的替代品(pytest.raises在本例中为 )。它实际上没有做任何事情,所以如果你想测试它没有引发特定的异常,你应该看到其他答案之一。

  • @MartijnPieters 暗示您可以通过像这样使用它来回答一般问题:`with does_not_raise(): foo(7)`。在我看来,这可以防止人们在阅读像裸露的“foo(7)”这样的无期望测试时出现停顿。 (2认同)

Ois*_*sin 6

我很想知道not_raises是否有效.对此的快速测试是(test_notraises.py):

from contextlib import contextmanager

@contextmanager
def not_raises(ExpectedException):
    try:
        yield

    except ExpectedException, err:
        raise AssertionError(
            "Did raise exception {0} when it should not!".format(
                repr(ExpectedException)
            )
        )

    except Exception, err:
        raise AssertionError(
            "An unexpected exception {0} raised.".format(repr(err))
        )

def good_func():
    print "hello"


def bad_func():
    raise ValueError("BOOM!")


def ugly_func():
    raise IndexError("UNEXPECTED BOOM!")


def test_ok():
    with not_raises(ValueError):
        good_func()


def test_bad():
    with not_raises(ValueError):
        bad_func()


def test_ugly():
    with not_raises(ValueError):
        ugly_func()
Run Code Online (Sandbox Code Playgroud)

它确实有效.但是我不确定它是否真的在测试中读得很好.

  • 虽然这为测试增加了可读的期望,但在我看来,“try- except”并没有提供附加值,因为如果引发该异常,测试仍然会出错而不是失败。似乎更好地使用“nullcontext”或“pytest.fail”,就像其他答案中一样。 (2认同)