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

时间:2014-04-28 09:33:45

标签: python unit-testing 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)

输出:

================================ 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函数中引发了异常的位置?

12 个答案:

答案 0 :(得分:225)

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会保存异常对象,以便您可以从中提取详细信息。例如,如果要检查异常调用堆栈或其他嵌套异常。

答案 1 :(得分:97)

你的意思是这样的:

def test_raises():
    with pytest.raises(Exception) as excinfo:   
        raise Exception('some info')   
    assert excinfo.value.message == 'some info' 

答案 2 :(得分:44)

有两种方法可以在pytest中处理这类情况:

  • 使用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=============================

正如documentation所说:

  

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

答案 3 :(得分:35)

你可以尝试

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

答案 4 :(得分:3)

此解决方案是我们正在使用的:

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,http://pytest.readthedocs.io/en/reorganize-docs/assert.html#assert

答案 5 :(得分:3)

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

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

答案 6 :(得分:1)

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

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

答案 7 :(得分:1)

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

  1. 使用pytest.raises编写有关引发的异常的断言
  2. 使用@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

还可以为raises指定一个pytest.mark.xfail自变量。

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可能会更好,例如记录未修复的文档错误或依赖项中的错误。

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

答案 8 :(得分:0)

您是否尝试删除“pytrace = True”?

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

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

答案 9 :(得分:0)

更好的做法是使用继承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

答案 10 :(得分:0)

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

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

答案 11 :(得分:0)

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

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

@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

通过这种方式,您可以优雅地失败测试,​​而不是因任何原因引发异常而导致测试崩溃。