pytest 核心功能-在测试中编写和报告断言
使用assert语句进行断言
pytest 允许您使用标准 Python 断言来验证 Python 测试中的期望和值。 例如,您可以编写以下内容:
# content of test_assert1.py
def f():
return 3
def test_function():
assert f() == 4
断言您的函数返回某个值。 如果此断言失败,您将看到函数调用的返回值:
$ pytest test_assert1.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
test_assert1.py F [100%]
================================= FAILURES =================================
______________________________ test_function _______________________________
def test_function():
> assert f() == 4
E assert 3 == 4
E + where 3 = f()
test_assert1.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_assert1.py::test_function - assert 3 == 4
============================ 1 failed in 0.12s =============================
Pytest支持显示最常用的子表达式的值,包括调用、属性、比较以及二进制和一元操作符。这允许您在不使用样板代码的情况下使用惯用的python构造,同时不丢失内省信息。
但是,如果您使用这样的断言指定消息:
assert a % 2 == 0, "value was odd, should be even"
然后,根本不会发生任何断言内省,消息将简单地显示在回溯中。
关于预期异常的断言
为了编写有关引发异常的断言,您可以使用 pytest.raises()
作为上下文管理器,如下所示:
import pytest
def test_zero_division():
with pytest.raises(ZeroDivisionError):
1 / 0
如果你需要访问实际的异常信息,你可以使用:
def test_recursion_depth():
with pytest.raises(RuntimeError) as excinfo:
def f():
f()
f()
assert "maximum recursion" in str(excinfo.value)
excinfo
是一个ExceptionInfo
实例,它包装了实际引发的异常。interest的主要属性是.type
、.value
和.traceback
您可以向上下文管理器传递一个match
关键字参数,以测试正则表达式是否匹配异常的字符串表示(类似于 unittest
中的 TestCase.assertRaisesRegex
方法):
import pytest
def myfunc():
raise ValueError("Exception 123 raised")
def test_match():
with pytest.raises(ValueError, match=r".* 123 .*"):
myfunc()
match
方法的 regexp
参数与 re.search
函数匹配,因此在上面的示例中 match='123'
也可以正常工作。
pytest.raises()
函数还有另一种形式,您可以在其中传递一个函数,该函数将使用给定的 *args
和 **kwargs
执行,并断言引发了给定的异常:
pytest.raises(ExpectedException, func, *args, **kwargs)
如果出现无异常或错误异常等故障,reporter
将为您提供有用的输出。
请注意,也可以为 pytest.mark.xfail
指定一个raises
参数,它以更具体的方式检查测试是否失败,而不仅仅是引发任何异常:
@pytest.mark.xfail(raises=IndexError)
def test_f():
f()
使用 pytest.raises()
对于您正在测试自己的代码故意引发的异常的情况可能会更好,而使用带有检查功能的@pytest.mark.xfail
可能更适合记录未修复的错误(其中测试描述了应该发生什么)或依赖项中的错误。
关于预期警告的断言
您可以使用 pytest.warns
检查代码是否引发了特定警告。
利用上下文相关的比较
Pytest具有丰富的支持,可以在遇到比较时提供上下文敏感的信息。例如:
# content of test_assert2.py
def test_set_comparison():
set1 = set("1308")
set2 = set("8035")
assert set1 == set2
如果你运行这个模块:
$ pytest test_assert2.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
test_assert2.py F [100%]
================================= FAILURES =================================
___________________________ test_set_comparison ____________________________
def test_set_comparison():
set1 = set("1308")
set2 = set("8035")
> assert set1 == set2
E AssertionError: assert {'0', '1', '3', '8'} == {'0', '3', '5', '8'}
E Extra items in the left set:
E '1'
E Extra items in the right set:
E '5'
E Use -v to get more diff
test_assert2.py:4: AssertionError
========================= short test summary info ==========================
FAILED test_assert2.py::test_set_comparison - AssertionError: assert {'0'...
============================ 1 failed in 0.12s =============================
对一些情况进行了特殊比较:
- 比较长字符串:显示上下文差异
- 比较长序列:第一个失败的索引
- 比较字典:不同的条目
为失败的断言定义你自己的解释
可以通过实现pytest_assertrepr_compare
钩子来添加您自己的详细解释。
pytest_assertrepr_compare(config, op, left, right)
返回失败的断言表达式中比较的解释。
如果没有自定义解释,则返回None
,否则返回一个字符串列表。字符串将由换行符连接,但字符串中的任何换行符将被转义。请注意,除第一行外的所有内容都稍微缩进,目的是将第一行作为摘要。
参数:
-
config (pytest.Config)
-- pytest 配置对象 -
op (str)
– -
left (object)
– -
right (object)
–
返回类型:
Optional[List[str]]
例如,可以考虑在conftest.py
文件中添加以下钩子,它提供了对Foo
对象的另一种解释:
# content of conftest.py
from test_foocompare import Foo
def pytest_assertrepr_compare(op, left, right):
if isinstance(left, Foo) and isinstance(right, Foo) and op == "==":
return [
"Comparing Foo instances:",
" vals: {} != {}".format(left.val, right.val),
]
现在,给定这个测试模块:
# content of test_foocompare.py
class Foo:
def __init__(self, val):
self.val = val
def __eq__(self, other):
return self.val == other.val
def test_compare():
f1 = Foo(1)
f2 = Foo(2)
assert f1 == f2
你可以运行test
模块,并获得在conftest
文件中定义的自定义输出:
$ pytest -q test_foocompare.py
F [100%]
================================= FAILURES =================================
_______________________________ test_compare _______________________________
def test_compare():
f1 = Foo(1)
f2 = Foo(2)
> assert f1 == f2
E assert Comparing Foo instances:
E vals: 1 != 2
test_foocompare.py:12: AssertionError
========================= short test summary info ==========================
FAILED test_foocompare.py::test_compare - assert Comparing Foo instances:
1 failed in 0.12s
断言内省细节
通过在assert
语句运行之前重写它们,可以报告关于失败断言的详细信息。重写的断言语句将自省信息放入断言失败消息中。pytest只重写由其测试收集过程直接发现的测试模块,因此在不属于测试模块的支持模块中的断言不会被重写。
您可以在导入模块之前通过调用 register_assert_rewrite
手动为导入的模块启用断言重写(这样做的好地方是在您的根目录 conftest.py
中)。
断言重写将文件缓存到硬盘上
pytest 会将重写的模块写回磁盘进行缓存。 您可以通过将其添加到 conftest.py
文件的顶部来禁用此行为(例如,避免在经常移动文件的项目中留下陈旧的 .pyc
文件):
import sys
sys.dont_write_bytecode = True
请注意,您仍然可以获得断言自省的好处,唯一的变化是 .pyc
文件不会缓存在磁盘上。
此外,如果无法写入新的 .pyc
文件,即在只读文件系统或 zip 文件中,重写将静默跳过缓存。
禁用断言重写
pytest 在导入时重写测试模块,方法是使用导入钩子编写新的 pyc
文件。大多数情况下,这是透明的。如果您自己使用导入,导入钩子可能会干扰。
如果是这种情况,你有两个选择:
- 通过将字符串
PYTEST_DONT_REWRITE
添加到其文档字符串中,禁用特定模块的重写。 - 使用
assert=plain
禁用所有模块的重写。
更多建议: