在py.test
(版本4.6.2)中,您有一个标记修饰符来进行测试以将其标记为失败,例如
@pytest.mark.xfail
def test1():
return 1/0
还可以验证异常本身
@pytest.mark.xfail(raises=ZeroDivisionError)
但是可以通过某种方式验证错误消息本身吗?
当您有HTTPError
时,这很有用,因为可能有很多原因。而且,当您比较错误消息本身时,可以更详细地说明测试何时失败(例如,将某个Client Error
与Server Error
区别开)。
到目前为止,我正在使用以下构造:
def test_fail_request(self):
with pytest.raises(requests.exceptions.HTTPError) as excinfo:
response = requests.something
assert '403 Client Error: Not Found' in str(excinfo.value)
但是,py.test
当然可以进行如下所示的测试,使其更具可读性,紧凑性和正确性:
@pytest.mark.xfail(expected_error = "403 Client Error: Not Found"):
def test_fail_request(self):
response = requests.something
有没有办法实现这种行为/功能?
为澄清起见,最后一个代码示例应该会失败,但仅在错误消息包含特定消息时才失败(示例:400 Client Error: Bad Request
)。在这种情况下,测试将报告为XFAIL
。
但是,如果测试失败并创建一条不同错误消息(即使是相同的异常,例如错误消息中的500 Server Error
),则该测试必须报告为“意外传递”(XPASS
)。
答案 0 :(得分:2)
如果失败是正常现象,则可以创建自己的装饰器,例如:
import functools
def expect_http_error(func=None, *, expected_error):
def wrapper(func):
@functools.wraps(func)
def inner(*args, **kwargs):
with pytest.raises(requests.exceptions.HTTPError) as excinfo:
func(*args, **kwargs)
assert expected_error in str(excinfo.value)
return inner
return wrapper if func is None else wrapper(func)
然后像这样使用它:
@expect_http_error(expected_error = "403 Client Error: Not Found")
def test_fail_request(self):
response = requests.something
答案 1 :(得分:0)
pytest
标记接受任意参数,因此使用自定义参数扩展内置标记非常容易:
@pytest.mark.xfail(raises=ZeroDivisionError, match='.* by zero')
def test():
return 1 / 0
尽管未为match
标记定义xfail
自变量,但是它将被存储,但是默认情况下保持未使用状态。然后,您可以在自定义hookimpls中访问该参数。
警告:
但是,如果测试失败并创建一条不同错误消息(即使是相同的异常,例如错误消息中的
500 Server Error
),则该测试必须报告为“意外传递”(XPASS
)。
请注意,pytest
如果引发意外类型的异常,则测试失败,例如:
@pytest.mark.xfail(raises=RuntimeError)
def test():
return 1 / 0
将报告为失败:
============================================== FAILURES ==============================================
_______________________________________________ test ________________________________________________
@pytest.mark.xfail(raises=RuntimeError)
def test():
> return 1 / 0
E ZeroDivisionError: division by zero
test_spam.py:18: ZeroDivisionError
请注意,由于您实际上想要xpass
测试会引发意外异常,因此该测试与pytest
标准行为有所不同。因此,我实现了以下钩子,如下所示:
如果测试带有“标准” xfail
标记(不带match
参数),例如
@pytest.mark.xfail(raises=Foo)
如果引发Foo
,则测试将失败,否则将失败(pytest
标准行为)
如果测试带有xfail
参数的match
标记,例如
@pytest.mark.xfail(raises=Foo, match='.*bar.*')
在所有其他情况下(例如,以不同的消息引发xfail
的情况下,如果引发Foo
且匹配.*bar.*
的正则表达式的邮件,将仅 Foo
,引发Bar
等),它将xpass
。
hookimpl:
# conftest.py
import re
from pytest import hookimpl
@hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
if not hasattr(item, '_evalxfail'):
return # no xfail marker
evalxfail = item._evalxfail
match_expr = evalxfail.get('match')
if match_expr is None:
return # no match argument in xfail marker
if call.excinfo and evalxfail.wasvalid() and evalxfail.istrue():
match = re.search(match_expr, str(call.excinfo.value))
# if the exception doesn't match the type or description, set to XPASS
if match or evalxfail.invalidraise(call.excinfo.value):
rep.outcome = 'passed'
rep.wasxfail = evalxfail.getexplanation()
要进行进一步的自定义,请更改挂钩中的条件检查。例如,如果您想:
xfail
(如果异常的类型正确且与消息正则表达式匹配)xpass
,如果异常的类型正确,但与正则表达式不匹配换行
if match or evalxfail.invalidraise(call.excinfo.value):
到
if match:
等