137

假设我们有这样的东西:

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)
         ....

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

def test_foo3():
    assert foo(7) == 7

但我想通过 pytest.raises() 进行测试。有可能吗?例如:在某种情况下,函数“foo”根本没有返回值,

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

以这种方式进行测试可能是有意义的,恕我直言。

4

4 回答 4

177

如果引发任何类型的意外异常,测试将失败。您可以只调用 foo(7) 并且您将测试没有引发 MyError。因此,以下就足够了:

def test_foo3():
    foo(7)

如果您想明确并为此编写断言语句,您可以执行以下操作:

def test_foo3():
    try:
        foo(7)
    except MyError:
        pytest.fail("Unexpected MyError ..")
于 2013-11-28T21:34:15.937 回答
36

建立在 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))

如果您想坚持raises拥有一个对应物,这样您的测试就更具可读性,这很好。然而,从本质上讲,除了运行要在自己的行上测试的代码块之外,您实际上并不需要任何东西——一旦该块引发错误,pytest 无论如何都会失败。

于 2017-02-19T12:13:58.727 回答
22

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

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

它类似于其他一些答案,但使用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

使用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)

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

于 2021-06-17T03:54:23.210 回答
8

我很想知道 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()

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

于 2016-02-17T13:37:13.203 回答