目录
2.2 如何在测试中编写和报告断言
2.2.1 使用assert语句断言
2.2.2 关于预期异常的断言
2.2.3 关于预期警告的断言
2.2.4 应用上下文相关的比较
2.2.5 为失败的断言定义自己的解释
2.2.6 断言内省细节
2.2 如何在测试中编写和报告断言
2.2.1 使用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支持显示最常见的子表达式的值,包括调用、属性、比较以及二进制和一元运算符。(请参阅使用pytest演示Python故障报告Demo of Python failure reports with pytest)。这允许您在不丢失内省信息的情况下使用惯用的python构造,而不需要样板代码。
但是,如果您指定一条消息,并使用以下断言:
assert a % 2 == 0, "value was odd, should be even"
那么根本不进行断言内省,消息将简单地显示在回溯中。
有关断言内省的更多信息,请参阅Assertion introspection details。
2.2.2 关于预期异常的断言
为了编写关于引发异常的断言,可以使用pytest.reses()作为上下文管理器,如下所示:
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实例,它是引发实际异常的包装器。感兴趣的主要属性是.type、.value和.traceback。
您可以将match关键字参数传递给上下文管理器,以测试正则表达式在异常的字符串表示形式上是否匹配(类似于unittest中的TestCase.assertRailesRegex方法):
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)
在出现诸如无异常或错误异常之类的故障时,报告器将为您提供有用的输出。
请注意,也可以为pytest.mark.xfail指定一个“raises”参数,该参数检查测试是否以引发任何异常更具体的方式失败:
@pytest.mark.xfail(raises=IndexError)
def test_f(): f()
使用pytest.reises()可能更适合于测试自己的代码故意引发的异常的情况,而使用带有check函数的@pytest.mark.xfail可能更适合记录未修复的错误(测试描述了“应该”发生什么)或依赖关系中的错误。
2.2.3 关于预期警告的断言
您可以使用pytest.warns检查代码是否引发特定警告。
2.2.4 应用上下文相关的比较
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 =============================
对一些情况进行了特别比较:
- 比较长字符串:显示上下文差异
- 比较长序列:首次失败指数
- 比较dicts:不同条目
有关更多示例,请参阅reporting demo。
2.2.5 为失败的断言定义自己的解释
可以通过实现pytest_asserrepr_compare来添加您自己的详细解释。
pytest_asserrepr_compare(config, op, left, right)
返回失败断言表达式中的比较说明。
返回None表示没有自定义解释,否则返回字符串列表。字符串将由换行符连接,但字符串中的任何换行符都将被转义。请注意,除第一行外的所有行都将略微缩进,目的是使第一行成为摘要
Parameters
• config (Config) – The pytest config object.
• op (str) – The operator, e.g. "==", "!=", "not in".
• left (object) – The left operand.
• right (object) – The right operand.
例如,考虑在conftest.py文件中添加以下,该文件为Foo对象提供了另一种解释:
# content of conftest.py
from test_foocompare import Foodef pytest_assertrepr_compare(op, left, right): if isinstance(left, Foo) and isinstance(right, Foo) and op == "==": return [ "Comparing Foo instances:", f" vals: {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.valdef test_compare(): f1 = Foo(1) f2 = Foo(2) assert f1 == f2
您可以运行测试模块并获得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
2.2.6 断言内省细节
关于失败断言的报告细节是通过在运行断言语句之前重写断言语句来实现的。重写断言语句将自省信息放入断言失败消息中。pytest只重写由其测试收集过程直接发现的测试模块,因此支持模块中的断言(本身不是测试模块)不会被重写。
您可以通过在导入模块之前调用register_assert_rewrite来手动启用导入模块的断言重写(在根conftest.py中是一个很好的方法)。
为了获得更多信息,Benjamin Peterson写了《pytest新断言重写的幕后》(Behind the scenes of pytest’s new assertion rewriting)。
断言重写在磁盘上缓存文件
pytest会将重写后的模块写回磁盘进行缓存。您可以通过将其添加到conftest.py文件的顶部来禁用此行为(例如,为了避免在大量移动文件的项目中留下过时的.pyc文件):
import sys
sys.dont_write_bytecode = True
请注意,您仍然可以获得断言自省的好处,唯一的变化是.pyc文件不会缓存在磁盘上。
此外,如果重写无法写入新的.pyc文件,即在只读文件系统或zipfile中,则重写将自动跳过缓存。
禁用断言重写
pytest在导入时通过使用导入挂钩写入新的pyc文件来重写测试模块。大多数情况下,这是透明的。但是,如果您自己使用导入机器,则导入钩子可能会干扰。
如果是这种情况,您有两种选择:
- 通过将字符串PYTEST_DONT_REWRITE添加到其文档字符串中,禁用对特定模块的重写。
- 使用--assert=plain禁用所有模块的重写。