如何正确断言在pytest中引发异常?

Gil*_*tes 246 python testing unit-testing pytest

码:

# coding=utf-8
import pytest


def whatever():
    return 9/0

def test_whatever():
    try:
        whatever()
    except ZeroDivisionError as exc:
        pytest.fail(exc, pytrace=True)
Run Code Online (Sandbox Code Playgroud)

输出:

================================ test session starts =================================
platform linux2 -- Python 2.7.3 -- py-1.4.20 -- pytest-2.5.2
plugins: django, cov
collected 1 items 

pytest_test.py F

====================================== FAILURES ======================================
___________________________________ test_whatever ____________________________________

    def test_whatever():
        try:
            whatever()
        except ZeroDivisionError as exc:
>           pytest.fail(exc, pytrace=True)
E           Failed: integer division or modulo by zero

pytest_test.py:12: Failed
============================== 1 failed in 1.16 seconds ==============================
Run Code Online (Sandbox Code Playgroud)

如何使pytest打印回溯,所以我会看到whatever函数中引发异常的位置?

小智 263

pytest.raises(Exception) 是你需要的.

import pytest

def test_passes():
    with pytest.raises(Exception) as e_info:
        x = 1 / 0

def test_passes_without_info():
    with pytest.raises(Exception):
        x = 1 / 0

def test_fails():
    with pytest.raises(Exception) as e_info:
        x = 1 / 1

def test_fails_without_info():
    with pytest.raises(Exception):
        x = 1 / 1

# Don't do this. Assertions are caught as exceptions.
def test_passes_but_should_not():
    try:
        x = 1 / 1
        assert False
    except Exception:
        assert True

# Even if the appropriate exception is caught, it is bad style,
# because the test result is less informative
# than it would be with pytest.raises(e)
# (it just says pass or fail.)

def test_passes_but_bad_style():
    try:
        x = 1 / 0
        assert False
    except ZeroDivisionError:
        assert True

def test_fails_but_bad_style():
    try:
        x = 1 / 1
        assert False
    except ZeroDivisionError:
        assert True
Run Code Online (Sandbox Code Playgroud)

产量

============================================================================================= test session starts ==============================================================================================
platform linux2 -- Python 2.7.6 -- py-1.4.26 -- pytest-2.6.4
collected 7 items 

test.py ..FF..F

=================================================================================================== FAILURES ===================================================================================================
__________________________________________________________________________________________________ test_fails __________________________________________________________________________________________________

    def test_fails():
        with pytest.raises(Exception) as e_info:
>           x = 1 / 1
E           Failed: DID NOT RAISE

test.py:13: Failed
___________________________________________________________________________________________ test_fails_without_info ____________________________________________________________________________________________

    def test_fails_without_info():
        with pytest.raises(Exception):
>           x = 1 / 1
E           Failed: DID NOT RAISE

test.py:17: Failed
___________________________________________________________________________________________ test_fails_but_bad_style ___________________________________________________________________________________________

    def test_fails_but_bad_style():
        try:
            x = 1 / 1
>           assert False
E           assert False

test.py:43: AssertionError
====================================================================================== 3 failed, 4 passed in 0.02 seconds ======================================================================================
Run Code Online (Sandbox Code Playgroud)

请注意,e_info保存异常对象以便从中提取详细信息.例如,如果要检查异常调用堆栈或其他嵌套异常.

  • 如果您可以包含一个实际查询`e_info`的示例,那将是一件好事.对于更熟悉某些其他语言的开发人员来说,`e_info`的范围扩展到`with`块之外并不明显. (25认同)
  • 对于严格或宽松的错误消息检查(除了检查异常类型之外),请使用“match”关键字参数 - 另请参阅此答案:/sf/answers/3959867341/ (5认同)
  • 如果您期望为测试引发异常,这非常有用。如果您的测试可能引发异常并且您希望优雅地处理它,那么它不是很有用。 (2认同)

小智 118

你的意思是这样的:

def test_raises():
    with pytest.raises(Exception) as excinfo:   
        raise Exception('some info')
    # these asserts are identical; you can use either one   
    assert execinfo.value.args[0] == 'some info'
    assert str(execinfo.value) == 'some info'
Run Code Online (Sandbox Code Playgroud)

  • `excinfo.value.message`对我不起作用,不得不使用`str(excinfo.value)`,添加了一个新答案 (14认同)
  • 从版本`3.1`开始,您可以使用关键字参数`match`来断言异常与文本或正则表达式匹配:`with raises(ValueError,match ='必须为0或None'):raise ValueError("value必须为0或无")`或`有引发(ValueError,match = r'必须是\ d + $'):引发ValueError("value必须为42") (10认同)
  • `assert excinfo.match(r"^some info$")` 也有效 (8认同)
  • @d_j,`assert excinfo.value.args [0] =='some info'`是访问消息的直接方式 (5认同)
  • @ChrisCollett - 如果您期望出现错误消息,并且在没有错误消息时抛出异常:这似乎是我希望我的测试执行的操作。这意味着错误消息在不应该的时候丢失了 (3认同)
  • 重要提示:如果在没有消息的情况下引发异常,则“execinfo.value.args”将是一个空元组,这意味着尝试执行“execinfo.value.args[0]”将引发异常。更安全的做法是 `message = execinfo.value.args[0] if execinfo.value.args else None; 断言消息=='一些信息'`。 (2认同)

ver*_*der 51

在pytest中有两种处理这种情况的方法:

  • 使用pytest.raises功能

  • 使用pytest.mark.xfail装饰

用法pytest.raises:

def whatever():
    return 9/0
def test_whatever():
    with pytest.raises(ZeroDivisionError):
        whatever()
Run Code Online (Sandbox Code Playgroud)

用法pytest.mark.xfail:

@pytest.mark.xfail(raises=ZeroDivisionError)
def test_whatever():
    whatever()
Run Code Online (Sandbox Code Playgroud)

产量pytest.raises:

============================= test session starts ============================
platform linux2 -- Python 2.7.10, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- 
/usr/local/python_2.7_10/bin/python
cachedir: .cache
rootdir: /home/user, inifile:
collected 1 item

test_fun.py::test_whatever PASSED


======================== 1 passed in 0.01 seconds =============================
Run Code Online (Sandbox Code Playgroud)

pytest.xfail标记输出:

============================= test session starts ============================
platform linux2 -- Python 2.7.10, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- 
/usr/local/python_2.7_10/bin/python
cachedir: .cache
rootdir: /home/user, inifile:
collected 1 item

test_fun.py::test_whatever xfail

======================== 1 xfailed in 0.03 seconds=============================
Run Code Online (Sandbox Code Playgroud)

正如文件所说:

pytest.raises对于您正在测试自己的代码故意提出的异常的情况,使用可能会更好,而使用@pytest.mark.xfailcheck函数可能更适合记录未修复的错误(测试描述应该发生什么)或依赖项中的错误.

  • 我必须重申 @Ctrl-C 的评论: pytest.mark.xfail 并不断言引发了异常,它只是允许引发异常。这不是问题标题所问的内容。 (7认同)
  • `xfail` 并不是问题的解决方案,它只是允许测试失败。在这里我们想检查是否引发了某个异常。 (5认同)

d_j*_*d_j 39

你可以试试

def test_exception():
    with pytest.raises(Exception) as excinfo:   
        function_that_raises_exception()   
    assert str(excinfo.value) == 'some info' 
Run Code Online (Sandbox Code Playgroud)

  • 要在 pytest 5.0.0 中以字符串形式获取异常消息/值,需要使用“str(excinfo.value)”。它也适用于 pytest 4.x。在 pytest 4.x 中,“str(excinfo)”也可以工作,但在 pytest 5.0.0 中*不*工作。 (2认同)

lmi*_*asf 27

处理异常的方法有两种pytest

  1. 用于pytest.raises编写有关引发异常的断言
  2. 使用@pytest.mark.xfail

1. 使用pytest.raises

来自文档

为了编写有关引发异常的断言,您可以使用pytest.raises上下文管理器

例子:

仅断言一个例外:

import pytest


def test_zero_division():
    with pytest.raises(ZeroDivisionError):
        1 / 0
Run Code Online (Sandbox Code Playgroud)

with pytest.raises(ZeroDivisionError)表示下一个代码块中的任何内容都应该引发ZeroDivisionError异常。如果没有引发异常,则测试失败。如果测试引发不同的异常,则测试失败。

如果您需要访问实际的异常信息:

import pytest

def f():
    f()

def test_recursion_depth():
    with pytest.raises(RuntimeError) as excinfo:
        f()
    assert "maximum recursion" in str(excinfo.value)
Run Code Online (Sandbox Code Playgroud)

excinfo是一个ExceptionInfo实例,它是引发的实际异常的包装器。感兴趣的主要属性是.type.value.traceback

2. 使用@pytest.mark.xfail

也可以指定一个raises参数pytest.mark.xfail

import pytest

@pytest.mark.xfail(raises=IndexError)
def test_f():
    l = [1, 2, 3]
    l[10]
Run Code Online (Sandbox Code Playgroud)

@pytest.mark.xfail(raises=IndexError)表示下一个代码块中的任何内容都应该引发IndexError异常。如果IndexError引发了,则测试标记为xfailed (x)。如果没有引发异常,则测试标记为xpassed (X)。如果测试引发不同的异常,则测试失败。

笔记:

  • 对于测试您自己的代码故意引发的异常的情况,使用pytest.raises可能会更好,而@pytest.mark.xfail对于记录未修复的错误或依赖项中的错误等情况,与检查函数一起使用可能会更好。

  • 您可以将match关键字参数传递给上下文管理器 ( pytest.raises) 以测试正则表达式是否与异常的字符串表示形式匹配。(查看更多


Jan*_*cke 6

pytest不断发展,并且随着最近的一次不错的变化,现在可以同时测试

  • 异常类型(严格测试)
  • 错误消息(使用正则表达式进行严格检查或宽松检查)

文档中的两个示例:

with pytest.raises(ValueError, match='must be 0 or None'):
    raise ValueError('value must be 0 or None')
Run Code Online (Sandbox Code Playgroud)
with pytest.raises(ValueError, match=r'must be \d+$'):
    raise ValueError('value must be 42')
Run Code Online (Sandbox Code Playgroud)

我已经在许多项目中使用了这种方法,并且非常喜欢它。

  • 这应该是公认的答案。它是最新的和“Pythonic”的,这意味着它简洁、易读且易于理解。 (2认同)
  • 由于消息的具体细节,我比“xfail”更喜欢这个,并且我们可以在一个测试用例中验证多个“raises” (2认同)

SMD*_*MDC 5

我们正在使用这个解决方案:

def test_date_invalidformat():
    """
    Test if input incorrect data will raises ValueError exception
    """
    date = "06/21/2018 00:00:00"
    with pytest.raises(ValueError):
        app.func(date) #my function to be tested
Run Code Online (Sandbox Code Playgroud)

请参考pytest,https://docs.pytest.org/en/latest/reference.html#pytest-raises


Ale*_*rub 5

正确的方法正在使用,pytest.raises但我在这里的评论中找到了有趣的替代方法,并希望将其保存以供将来该问题的读者使用:

try:
    thing_that_rasises_typeerror()
    assert False
except TypeError:
    assert True
Run Code Online (Sandbox Code Playgroud)

  • 这是不好的风格。如果“事物”没有引发错误,或者引发意外错误,您将得到断言 False,而没有任何测试报告或分析的上下文。pytest 回溯在断言处停止,目前尚不清楚试图测试什么。即使最初的评论也提到有更好的方法,但“只是为了展示编写测试的速度有多快”。为什么在没有这种背景的情况下这个“值得保存”? (4认同)