pytest 核心功能-在测试中编写和报告断言

2022-03-18 09:51 更新

使用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​禁用所有模块的重写。


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号