ChatGPT解决这个技术问题 Extra ChatGPT

如何正确断言在 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)

输出:

================================ 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 ==============================

如何使 pytest 打印回溯,所以我会看到 whatever 函数在哪里引发了异常?


p
phoenix

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

输出

============================================================================================= 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 ======================================================================================

请注意,e_info 保存异常对象,以便您可以从中提取详细信息。例如,如果要检查异常调用堆栈或内部的另一个嵌套异常。


如果您可以包含一个实际查询 e_info 的示例,那就太好了。对于更熟悉某些其他语言的开发人员来说,e_info 的范围延伸到 with 块之外并不明显。
如果您期望为您的测试引发异常,这很有用。如果您的测试可能引发异常并且您想优雅地处理它,这不是很有用。
对于严格或松散的错误消息检查(除了检查异常类型),请使用 match 关键字参数 - 另请参阅此答案:stackoverflow.com/a/56569533/145400
N
Neuron

你的意思是这样的:

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

assert excinfo.match(r"^some info$") 也可以
从版本 3.1 开始,您可以使用关键字参数 match 断言异常匹配文本或正则表达式:with raises(ValueError, match='must be 0 or None'): raise ValueError("value must be 0 or None")with raises(ValueError, match=r'must be \d+$'): raise ValueError("value must be 42")
重要提示:如果在没有消息的情况下引发异常,execinfo.value.args 将是一个空元组,这意味着尝试执行 execinfo.value.args[0] 将引发异常。做message = execinfo.value.args[0] if execinfo.value.args else None; assert message == 'some info'更安全。
D
Dr. Jan-Philip Gehrcke

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

异常类型(严格测试)

错误消息(使用正则表达式进行严格或松散检查)

文档中的两个示例:

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

我一直在许多项目中使用这种方法并且非常喜欢它。

注意:ilya-rusinThis comment 也建议使用上述方法。


这应该是公认的答案。它是最新的和“Pythonic”,这意味着它简洁、易读且易于理解。
v
veri_pudicha_coder

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

使用 pytest.raises 函数

使用 pytest.mark.xfail 装饰器

正如 documentation 所说:

对于您正在测试自己的代码故意引发的异常的情况,使用 pytest.raises 可能会更好,而使用带有检查功能的@pytest.mark.xfail 可能更适合记录未修复的错误(其中测试描述了什么) “应该”发生)或依赖项中的错误。

pytest.raises 的用法:

def whatever():
    return 9/0
def test_whatever():
    with pytest.raises(ZeroDivisionError):
        whatever()

pytest.mark.xfail 的用法:

@pytest.mark.xfail(raises=ZeroDivisionError)
def test_whatever():
    whatever()

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 =============================

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=============================

xfail 不是这里问题的解决方案,它只是让测试失败。在这里,我们想检查是否引发了某个异常。
我必须重申@Ctrl-C 的评论:pytest.mark.xfail 不会断言引发了异常,它只是允许引发异常。这不是问题的标题所要求的。
事实上,这个答案具有误导性。解释 xfail 的文档段落应该移到顶部。
d
d_j

你可以试试

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

要在 pytest 5.0.0 中将异常消息/值作为字符串获取,需要使用 str(excinfo.value)。它也适用于 pytest 4.x。在 pytest 4.x 中,str(excinfo) 也有效,但 not 在 pytest 5.0.0 中有效。
l
lmiguelvargasf

pytest 中有两种处理异常的方法:

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

1. 使用 pytest.raises

the docs

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

例子:

断言只是一个例外:

import pytest


def test_zero_division():
    with pytest.raises(ZeroDivisionError):
        1 / 0

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)

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

2.使用@pytest.mark.xfail

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

import pytest

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

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

笔记:

使用 pytest.raises 可能会更好地测试您自己的代码故意引发的异常,而使用带有检查功能的@pytest.mark.xfail 可能更适合记录未修复的错误或依赖项中的错误。

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


A
Alexey Shrub

正确的方法是使用 pytest.raises,但我在评论 here 中发现了有趣的替代方式,并希望将其保存以供将来阅读此问题的读者使用:

try:
    thing_that_rasises_typeerror()
    assert False
except TypeError:
    assert True

这是不好的风格。如果“事物”没有引发错误,或引发意外错误,则您会在没有任何测试报告或分析上下文的情况下得到 assert False。 pytest traceback 在断言处停止,目前尚不清楚要测试的内容。甚至最初的评论也提到有更好的方法,但“只是为了展示编写测试的速度有多快”。为什么在没有这种背景的情况下这“值得保存”?
S
SMDC

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

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

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


E
Elendurwen

如果要测试特定的错误类型,请结合使用 try、catch 和 raise:

#-- test for TypeError
try:
  myList.append_number("a")
  assert False
except TypeError: pass
except: assert False

r
rrlamichhane

如果您期望为您的测试用例引发异常,则此处提交的最佳答案很有用。如果您的测试可能引发异常并且您希望在任何一种情况下都优雅地处理它,那么这不是很有用。

如果您有一个可能(不会)引发异常的测试用例,我认为这可能是一个更好的选择。

@python.mark.parametrize("request_query, response_code", query_response_dataset)
def test_big_query_submission(request_query, response_code):
    try:
        stats = bigquery.Client().query(request_query)
    except Exception as e:
        assert False, f"Exception raised: {e}"
    assert stats.errors is None

这样,您就可以优雅地失败测试,而不是由于任何原因引发的异常导致测试崩溃。


k
kerbelp

更好的做法是使用继承 unittest.TestCase 并运行 self.assertRaises 的类。

例如:

import unittest


def whatever():
    return 9/0


class TestWhatEver(unittest.TestCase):

    def test_whatever():
        with self.assertRaises(ZeroDivisionError):
            whatever()

然后你可以通过运行来执行它:

pytest -vs test_path

比什么更好的练习?我根本不会使用 unittest 语法而不是 pytest 语法来称呼“更好的做法”。
它可能不是“更好”,但它是一个有用的选择。因为答案的标准是实用性,所以我赞成。
看起来 pytestnosex 更受欢迎,但这就是我使用 pytest 的方式。
S
Sue Yorgen

我只是写了一个钩子,每个测试都会得到

钩子:

@pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item: Item, call: CallInfo):

    outcome = yield  # The result after the test is completed
    result = outcome.get_result()

    if result.when == "call":
       if result.failed == True:

       else:

我有一个函数可以使用测试结果更新文档并使用以下方法添加跟踪:result.longrepr.reprcrash.message

我不知道这是否是最好的方法,但它确实回答了如何仅使用 pytest 打印回溯的问题

@显然我还有一些其他代码


L
Loic Pantou

您是否尝试删除 "pytrace=True" ?

pytest.fail(exc, pytrace=True) # before
pytest.fail(exc) # after

您是否尝试过使用 '--fulltrace' 运行?