pytest学习2: The writing and reporting of assertions in tests
文章目录
测试中断言的编写和报告
1. Asserting with the assert
statement
断言 assert
陈述
pytest
允许你使用标准的python assert
用于验证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-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
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:5: AssertionError
========================= 1 failed in 0.12 seconds =========================
pytest
支持显示最常见的子表达式的值,包括调用、属性、比较以及二进制和一元运算符。(见 用pytest演示python失败报告 )这允许你在不丢失自省信息的情况下使用不带样板代码的惯用python构造。
但是,如果使用如下断言指定消息:
assert a % 2 == 0, "value was odd, should be even"
这样根本不进行断言内省,消息将简单地显示在追溯中。
2. Assertions about expected exceptions
关于预期异常的断言
为了编写有关引发的异常的断言,可以使用 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
实例,它是引发的实际异常的包装。感兴趣的主要特征是 .type
, .value
和 .traceback
.
你可以通过给上下文管理器传递一个match
关键字参数,用于测试正则表达式是否匹配异常的字符串表示形式(类似于 unittest
中的 TestCase.assertRaisesRegexp
方法):
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)
如果出现故障,如 no exception or wrong exception ,这份报告将为你提供有益的输出。
请注意,也可以将“引发”参数指定为 pytest.mark.xfail
,它检查测试是否以比引发任何异常更具体的方式失败::
@pytest.mark.xfail(raises=IndexError)
def test_f():
f()
使用 pytest.raises
对于测试自己代码中故意引发的异常的情况,可能会更好。而使用 @pytest.mark.xfail
使用check函数可能更适合记录未修复的错误(测试描述了“应该”发生的情况)或依赖项中的错误。
3. Making use of context-sensitive comparisons
利用上下文相关的比较
当遇到比较时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-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
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 the full diff
test_assert2.py:5: AssertionError
========================= 1 failed in 0.12 seconds =========================
对一些情况进行特殊比较:
- 比较长字符串:显示文本差异
- 比较长序列:第一个失败下标
- 比较字典:不同的条目
4. Defining your own explanation for failed assertions
为失败的断言定义自己的解释
可以通过执行 pytest_assertrepr_compare
钩子来增加自己细节解释。
-
pytest_assertrepr_compare
(config, op, left, right)返回失败的断言表达式中比较解释。如果没有自定义解释,则返回“None”,否则返回字符串列表。字符串将由换行符联接,但任何字符串中的换行符将被转义。请注意,除了第一行之外,其他所有行都将略微缩进,目的是为了将第一行作为摘要。
例如,考虑在 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: %s != %s' % (left.val, right.val)]
现在,假设这个测试模块:
# content of test_foocompare.py
class Foo(object):
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
你可以运行测试模块并在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:11: AssertionError
1 failed in 0.12 seconds
5. Assertion introspection details
断言自省详细信息
通过在运行断言语句之前重写它们,可以获得有关失败断言的报告详细信息。重写的断言语句将自省信息放入断言失败消息中。 pytest
只重写由其测试收集过程直接发现的测试模块,因此 支持模块中的断言(本身不是测试模块)将不会被重写 .
你可以手动使能断言,为一个导入的模块重写,这个模块在导入前通过register_assert_rewrite调用。(一个很好的地方是在您的根目录中 conftest.py
)
6. Assertion rewriting caches files on disk
断言重写将文件缓存在磁盘上
pytest
将重写的模块写回磁盘进行缓存。你可以通过将这添加到conftest.py
文件的最顶端,来禁用此行为(例如,为了避免在经常移动文件的项目中留下过时的.pyc文件)
import sys
sys.dont_write_bytecode = True
请注意,你仍然可以获得断言内省的好处,唯一的变化是 .pyc
文件不会缓存在磁盘上。
此外,如果重写无法写入新的 .pyc
文件,它将自动跳过缓存,即只读文件系统或压缩文件中的文件。
7. Disabling assert rewriting
禁用断言重写
pytest
通过使用导入钩子hook的方式写入新的 pyc
文件夹,在导入时重写测试模块。大多数时候这是透明的。但是,如果你自己操作导入机制,导入钩子可能会产生干扰。
如果是这种情况,你有两个选择:
- 通过添加字符串
PYTEST_DONT_REWRITE
到它的docstring,禁用特定模块的重写 。 - 通过使用
--assert=plain
,禁用所有模块的重写.- 添加断言重写作为一个可替换的自省机制
- 介绍
--assert
选项。不赞成使用--no-assert
和--nomagic
. - 移除
--no-assert
和--nomagic
选项。移除--assert=reinterp
选项。
上一篇: pytest高阶用法一
下一篇: pytest基本使用