pytest官⽅⽂档6.2中⽂翻译版(第四章):测试中断⾔的编写和报告
测试中断⾔的编写和报告
4.1 使⽤assert声明来断⾔
pytest允许你在测试中使⽤标准的assert关键字来验证期望值和实际结果。例如你可以编写下⾯的代码:
# test_assert1.py
def f():
return 3
def test_function():
assert f() == 4
这段代码⽤于验证你的函数返回了⼀个特定的值。如果断⾔失败了你能够看到函数的实际返回值。
$ pytest test_assert1.py
=========================== test session starts ============================
platform linux -- , , ,
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: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"
内置的回显信息会被⾃定义信息替换,⾃定义的信息会被显⽰出来。
在 Assertion introspection details(内置断⾔的细节)这⼀节中我们将看到更多有关于断⾔的信息。
4.2 预研期望的异常
为了为引发的异常编写断⾔,你可以使⽤ pytest.raise() 作为 context manager(上下⽂管理器),像下⾯这样:
译者注:context manager 就是with后⾯跟着的对象,想要了解更多可以看这个⽂章
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是⼀个异常信息对象,它是⼀个真实引发的异常的包装。我们⽐较关⼼的最主要的属性是:.type .value .traceback
你可以给上下⽂管理器传递⼀个关键字匹配参数,去测试异常的字符串表⽰是不是符合正则表达式(与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() 的调⽤形式:你传递⼀个function,他会使⽤给定的 *args 和 **kwargs 去执⾏函数,判断指定的异常是否执⾏。
pytest.raises(ExpectedException, func, *args, **kwargs)
最终报告针对没有异常引发和引发了错误的异常会有⼗分有⽤的输出。
注意也有可能给pytest.mark.xfail指定⼀个具体的raises参数,⽤于检测在特定环境下的失败⽽不仅仅是引发⼀个异常:
@pytest.mark.xfail(raises=IndexError)
def test_f():
f()
pytest.raises()⽤在你故意引发某种异常的情况下更好,⽽ @pytest.mark.xfail 加⼀个测试函数的⽅法更适⽤于记录为解决的BUG(记录就应该出现的BUG)或依赖库的BUG。
译者注:我们可以从字⾯意思和最终运⾏结果加深对于pytest.raises() 和 @pytest.mark.xfail的理解,前者判断某个异常有没有引发,引发了这个测试就通过了,那字⾯意思其实更强调“这⾥引发了这个异常就对了”,后者如果代码中引发了指定的异常,结果是xfailed,没有引发异常,结果是xpassed,它的意思更像是“这⾥有⽑病,我要记录⼀下”
4.3 对于期望的警告的断⾔
你可以使⽤ pytest.warns 来测试引发特定警告的情况。
译者注:pytest.warns的使⽤在之后会讲到,下⾯放⼀个例⼦来提前了解⼀下,该代码并不在⼿册中
import warnings
import pytest
def test_warning():
with pytest.warns(UserWarning):
warnings.warn("my warning", UserWarning)
4.4 上下⽂敏感的⽐较的使⽤
当遇到⽐较的时候,pytest能够提供丰富的上下⽂敏感的信息,⽐如:
译者注:“上下⽂敏感的⽐较”的意思是当我们断⾔两个元素相等的时候,pytest会根据上下⽂来看如何⽐较,即 assert a == b 的时候,pytest会根据a和b的类型来智能的判断
# test_assert2.py
def test_set_comparison():
set1 = set("1308")
set2 = set("8035")
assert set1 == set2
译者注:这段代码体现了两个set的⽐较
如果你运⾏这段代码:
$ pytest test_assert2.py
=========================== test session starts ============================
platform linux -- , , ,
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:6: AssertionError
========================= short test summary info ==========================
FAILED test_assert2.py::test_set_comparison - AssertionError: assert {'0'...
============================ 1 failed in 0.12s =============================
有⼀些情况的⽐较⽐较特殊:
⽐较长字符串:显⽰上下⽂差异
⽐较长序列:第⼀次失败的索引(译者注:python的序列(sequence)有tuple和list两种)
⽐较字典:不同的条⽬
要看到更多例⼦,看测试报告⼀节的demo。
译者注:上⾯告诉了我们这⼏个不同的类型⽐较起来的逻辑是怎样的,字符串的⽐较就是⽐较两个字符串是不是⼀模⼀样,序列的⽐较,会告诉你不相同的元素索引是多少,字典的⽐较,会告诉你那个元素不⼀样,字典跟索引就没关系了,下⾯给出⼀个例⼦,这个例⼦并不在⼿册中,是译者为了⽅便⼤家理解上⾯的描述⽽写的
def test_compare_1():
assert '1234' == '1235'
def test_compare_2():
assert [1, 2, 3] == [1, 2, 4]
def test_compare_3():
assert ('123', 223) == ('23', 223)
def test_compare_4():
assert {'1': '123', '2': '223'} == {'1': '123', '2': '224'}
运⾏结果:
C:\Users\xx\testcase>pytest test_compare.py
============================================================== test session starts ============================================= ==================
platform win32 -- Python 3.7.7, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: C:\Users\WuHao\Desktop\1
collected 4 items
test_compare.py FFFF                                                                                                                        [100%]
==================================================================== FAILURES ============================================= =======================
_________________________________________________________________ test_compare_1 ___________________________________________ ______________________
def test_compare_1():
>      assert '1234' == '1235'
E      AssertionError: assert '1234' == '1235'
E        - 1235
E        ?    ^
E        + 1234
E        ?    ^
test_compare.py:2: AssertionError
_________________________________________________________________ test_compare_2 ___________________________________________ ______________________
def test_compare_2():
>      assert [1, 2, 3] == [1, 2, 4]
E      assert [1, 2, 3] == [1, 2, 4]
E        At index 2 diff: 3 != 4
E        Use -v to get the full diff
test_compare.py:6: AssertionError
_________________________________________________________________ test_compare_3 ___________________________________________ ______________________
def test_compare_3():
>      assert ('123', 223) == ('23', 223)
E      AssertionError: assert ('123', 223) == ('23', 223)
E        At index 0 diff: '123' != '23'
E        Use -v to get the full diff
test_compare.py:10: AssertionError
_________________________________________________________________ test_compare_4 ___________________________________________ ______________________
def test_compare_4():
>      assert {'1': '123', '2': '223'} == {'1': '123', '2': '224'}
E      AssertionError: assert {'1': '123', '2': '223'} == {'1': '123', '2': '224'}
E        Omitting 1 identical items, use -vv to show
E        Differing items:
E        {'2': '223'} != {'2': '224'}
E        Use -v to get the full diff
test_compare.py:14: AssertionError
============================================================ short test summary info ========================================== ===================
FAILED test_compare.py::test_compare_1 - AssertionError: assert '1234' == '1235'
FAILED test_compare.py::test_compare_2 - assert [1, 2, 3] == [1, 2, 4]
FAILED test_compare.py::test_compare_3 - AssertionError: assert ('123', 223) == ('23', 223)
FAILED test_compare.py::test_compare_4 - AssertionError: assert {'1': '123', '2': '223'} == {'1': '123', '2': '224'}
=============================================================== 4 failed in 0.23s ============================================== ==================
4.5 定义你⾃⼰的失败断⾔说明
通过实现 pytest_assertrepr_compare 钩⼦,我们可以添加我们⾃⼰的说明细节。
pytest_assertrepr_compare(config: Config, op: str, left: object, right: object) → Optional[List[str]]
在测试失败的表达式中返回⽐较的解释
在没有⾃定义说明的时候返回None,否则返回⼀个字符串列表。字符串会被换⾏符连接,如果字符串中有换⾏符,将会被转义。注意除了第⼀⾏其他⾏会被轻微的缩进,这样做的⽬的是让第⼀⾏成为⼀个摘要。
它的参数 config (_fig.Config) 是 pytest 的config对象
可以考虑在conftest.py中添加下⾯的钩⼦,来给Foo对象提供⼀个⾃定义的解释:
# 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),
]
然后,使⽤下⾯的测试模块:
# 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
你可以运⾏这个测试模块,得到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 ==========================
python官方文档中文版FAILED test_foocompare.py::test_compare - assert Comparing Foo instances:
1 failed in 0.12s
4.6 内置的断⾔细节
通过在assert语句运⾏之前重写它们,可以报告有关失败断⾔的详细信息。重写assert可以把内置的信息放进断⾔失败信息中去。pytest只会重写那些由它的测试⽤例发现程序直接发现的测试⽤例,所以模块如果不是可以被发现的测试程序则不会被重写。
通过在导⼊模块之前调⽤register_assert_rewrite,可以⼿动启⽤导⼊模块的断⾔重写(在根⽬录contest .py中是⼀个很好的位置)。
更多信息,可以参考Benjamin Peterson 写的 Behind the scenes of pytest’s new assertion rewriting
4.6.1 磁盘上缓存⽂件的断⾔重写

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。