我如何正确地断言 pytest 中引发了异常?
- 2025-02-14 09:49:00
- admin 原创
- 73
问题描述:
代码:
# 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
引发异常的位置?
解决方案 1:
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
保存异常对象以便您可以从中提取详细信息。例如,如果您想检查异常调用堆栈或其中的另一个嵌套异常。
解决方案 2:
你的意思是这样的吗:
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'
解决方案 3:
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-rusin的评论也建议了上述方法。
解决方案 4:
在 pytest 中有两种方法可以处理此类情况:
使用
pytest.raises
函数使用
pytest.mark.xfail
装饰器
正如文档所述:
对于测试您自己的代码故意引发的异常的情况,使用
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=============================
解决方案 5:
你可以尝试
def test_exception():
with pytest.raises(Exception) as excinfo:
function_that_raises_exception()
assert str(excinfo.value) == 'some info'
解决方案 6:
有两种方法可以处理异常pytest
:
用于
pytest.raises
编写有关引发的异常的断言使用
@pytest.mark.xfail
1. 使用pytest.raises
来自文档:
为了编写有关引发异常的断言,你可以使用
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
)来测试正则表达式是否与异常的字符串表示匹配。(查看更多)
解决方案 7:
正确的方法是使用,但我在这里的pytest.raises
评论中发现了有趣的替代方法,并希望将其保存以供将来阅读该问题的读者使用:
try:
thing_that_rasises_typeerror()
assert False
except TypeError:
assert True
解决方案 8:
我们正在使用的解决方案是:
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
解决方案 9:
如果要测试特定的错误类型,请结合使用 try、catch 和 raise:
#-- test for TypeError
try:
myList.append_number("a")
assert False
except TypeError: pass
except: assert False
解决方案 10:
只是添加另一个“愚蠢”的建议,因为我在现有答案中没有看到它。本质上,您可以将错误变量初始化为None
,在 try/except 块中执行操作,然后检查错误变量的类/值
e = None
try:
blah()
except Exception as exc:
e = exc
assert e.__class__ == ValueError # or whatever you expect
assert str(e) == "expected message"
解决方案 11:
如果您预计测试用例会引发异常,那么此处提交的最佳答案将非常有用。如果您的测试可能会引发异常,并且您希望在两种情况下都能妥善处理它,那么它就没什么用了。
如果您有一个可能(不会)引发异常的测试用例,我认为这可能是一个更好的选择。
@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
这样,您就可以优雅地失败测试,而不是因为任何原因引发异常而导致测试崩溃。
解决方案 12:
您是否尝试过删除“pytrace=True”?
pytest.fail(exc, pytrace=True) # before
pytest.fail(exc) # after
您是否尝试过使用“--fulltrace”运行?
解决方案 13:
更好的做法是使用继承 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
解决方案 14:
我刚写了一个钩子,每个测试都会得到
钩子:
@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 打印回溯的问题
@Obviously我还有一些其他代码
扫码咨询,免费领取项目管理大礼包!