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


292

码:

# 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函数中引发异常的地方?


我得到了整个追溯,Ubuntu 14.04,Python 2.7.6
thefourtheye 2014年

@thefourtheye请根据输出使要领。我尝试使用Python 2.7.4和Ubunthu 14.04-与我在主帖子中描述的结果相同。
Gill Bates

1
@GillBates您可以标记正确答案吗??
贡萨洛·加西亚

Answers:


329

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


34
如果您可以包括一个实际查询的示例,那就太好了e_info。对于更熟悉某些其他语言的开发人员来说,e_info扩展的范围并不超出该with块并不明显。
cjs

151

您的意思是这样的吗:

def test_raises():
    with pytest.raises(Exception) as execinfo:   
        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'

16
excinfo.value.message不适用于我,不得不使用str(excinfo.value),添加了一个新答案
d_j

5
@d_j assert excinfo.value.args[0] == 'some info'是访问消息的直接方法
maxschlepzig

assert excinfo.match(r"^some info$")也可以正常工作
罗宾·内默斯

14
从版本开始,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")
Ilya Rusin

58

有两种方法可以在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=============================

文档所述

使用pytest.raises很可能是更好的为你在哪里测试异常你自己的代码被刻意提高的情况下,而使用@pytest.mark.xfail具有校验功能可能是更好的东西,像记录未修正的错误(如测试描述什么是“应该”发生)或错误的依赖。


xfail并不是解决问题的方法,它只是允许测试失败。在这里,我们要检查是否引发了某种异常。
Ctrl-C

44

你可以试试

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)也可以,但是并没有在pytest 5.0.0工作。
Makyen

这就是我想要的。谢谢
Eystein再见

15

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

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


6

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

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

4

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

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


2

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

4
比什么更好的做法?我根本不会使用unittest语法而不是pytest语法来称呼“更好的实践”。
让·弗朗索瓦·科贝特

2
它可能不会“更好”,但是它是一个有用的选择。因为答案的标准是实用性,所以我很反对。
Reb.Cabin '18

看起来pytest比更为流行nosex,但这就是我使用pytest的方式。

0

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

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.