Pytest第三章(参考指南1)

目录

回到目录

上一章

第三章 参考指南

3.1 Fixtures参考

3.1.1 内置fixture

fixture使用 @pytest.fixture 装饰器定义。Pytest 有几个有用的内置fixture:

  • capfd

    • 以文本形式捕获输出到文件描述符 1 和 2。
  • capfdbinary

    • 以字节形式捕获输出到文件描述符 1 和 2。
  • caplog

    • 控制日志记录并访问日志条目。
  • capsys

    • 以文本形式捕获输出到 sys.stdout 和 sys.stderr。
  • capsysbinary

    • 以字节形式捕获输出到 sys.stdout 和 sys.stderr。
  • cache

    • 在 pytest 运行之间存储和检索值。
  • doctest_namespace

    • 提供一个注入到 doctests 名空间的字典。
  • monkeypatch

    • 临时修改类、函数、字典、os.environ 对象。
  • pytestconfig

    • 访问配置值、插件管理器和插件挂钩。
  • record_property

    • 向测试添加额外属性。
  • record_testsuite_property

    • 向测试套件添加额外属性。
  • recwarn

    • 记录测试函数发出的警告。
  • request

    • 提供有关正在执行的测试函数的信息。
  • testdir

    • 提供一个临时测试目录,以帮助运行和测试 pytest 插件。
  • tmp_path

    • 为每个测试函数提供一个唯一的临时目录的 pathlib.Path 对象。
  • tmp_path_factory

    • 创建会话范围的临时目录并返回 pathlib.Path 对象。
  • tmpdir

    • 为每个测试函数提供一个唯一的临时目录的 py.path.local 对象;已被 tmp_path 替换。
  • tmpdir_factory

    • 创建会话范围的临时目录并返回 py.path.local 对象;已被 tmp_path_factory 替换。

3.1.2 fixture可用性

fixture的可用性是从测试的角度来确定的。只有当测试在fixture定义的作用域内时,测试才能请求该fixture。如果一个fixture定义在一个类中,那么只有该类中的测试才能请求它。但如果一个fixture定义在模块的全局作用域中,那么该模块中的每个测试,即使它定义在一个类中,也可以请求它。

同样地,一个测试也只有在其与自动使用fixture定义在同一作用域内时才会受到该自动使用fixture的影响(参见"自动使用fixture在其作用域内首先执行")。

一个fixture还可以请求其他任何fixture,无论它在哪里定义,只要请求它的测试能够看到所有涉及的fixture。

例如,这里有一个测试文件,其中包含一个fixture(outer),它从一个未定义的范围中请求另一个fixture(inner):

python 复制代码
from __future__ import annotations

import pytest

@pytest.fixture
def order():
    return []

@pytest.fixture
def outer(order, inner):
    order.append("outer")

class TestOne:
    @pytest.fixture
    def inner(self, order):
        order.append("one")

    def test_order(self, order, outer):
        assert order == ["one", "outer"]

class TestTwo:
    @pytest.fixture
    def inner(self, order):
        order.append("two")

    def test_order(self, order, outer):
        assert order == ["two", "outer"]

这段代码展示了如何在一个测试文件中使用fixture,并且如何从一个fixture中请求另一个fixture。

从测试的角度来看,它们可以毫无问题地看到每个依赖的装置:

因此,当它们运行时,outer 可以毫无问题地找到 inner,因为 pytest 会从测试的角度进行搜索。

注释

定义装置的作用域与其实例化的顺序无关:顺序由描述的逻辑决定。

conftest.py:在多个文件之间共享fixture

conftest.py 文件的作用是为整个目录提供fixture。定义在 conftest.py 中的fixture可以被该包中的任何测试使用,而无需导入它们(pytest 会自动发现它们)。

你可以有多个嵌套的目录/包来包含你的测试,每个目录都可以有自己的 conftest.py 文件,其中包含自己的fixture,这些fixture会添加到父目录中的 conftest.py 文件提供的fixture中。

例如,给定一个如下所示的测试文件结构:

plaintext 复制代码
tests/
    __init__.py
    conftest.py
        # content of tests/conftest.py
        import pytest
        
        @pytest.fixture
        def order():
            return []
        
        @pytest.fixture
        def top(order, innermost):
            order.append("top")

    test_top.py
        # content of tests/test_top.py
        import pytest
        
        @pytest.fixture
        def innermost(order):
            order.append("innermost top")
        
        def test_order(order, top):
            assert order == ["innermost top", "top"]

subpackage/
    __init__.py
    conftest.py
        # content of tests/subpackage/conftest.py
        import pytest
        
        @pytest.fixture
        def mid(order):
            order.append("mid subpackage")

    test_subpackage.py
        # content of tests/subpackage/test_subpackage.py
        import pytest
        
        @pytest.fixture
        def innermost(order, mid):
            order.append("innermost subpackage")
        
        def test_order(order, top):
            assert order == ["mid subpackage", "innermost subpackage", "top"]

这段代码展示了如何在多个文件之间共享fixture,并且如何在不同的层次上定义和使用fixture。

范围的边界可以可视化如下:

目录本身成为了一种范围,其中在 conftest.py 文件中定义的fixture对该整个范围可用。

测试允许向上搜索(即跨出一个圆圈)以查找fixture,但不能向下(即跨入一个圆圈)继续其搜索。因此,tests/subpackage/test_subpackage.py 中的 test_order 可以找到在 tests/subpackage/test_subpackage.py 中定义的 innermost 定装置,但在 tests/test_top.py 中定义的 innermost fixture对它来说是不可用的,因为它需要降级(即跨入一个圆圈)才能找到它。

测试找到的第一个fixture将是被使用的fixture,因此如果需要为特定范围更改或扩展某个fixture的功能,fixture可以被覆盖。

你还可以使用 conftest.py 文件来实现本地按目录插件。

来自第三方插件的fixture

fixture不必在这个结构中定义即可在测试中使用。它们也可以由安装的第三方插件提供,这就是许多 pytest 插件的工作方式。只要这些插件已安装,它们提供的fixture可以从测试套件中的任何地方请求。

由于它们是从测试套件的结构之外提供的,第三方插件实际上并不像 conftest.py 文件和测试套件中的目录那样提供范围。因此,pytest 将按照之前解释的方式跨范围搜索fixture,最后才到达插件中定义的fixture。

例如,给定以下文件结构:

plaintext 复制代码
tests/
    __init__.py
    conftest.py
        # content of tests/conftest.py
        import pytest
        
        @pytest.fixture
        def order():
            return []

subpackage/
    __init__.py
    conftest.py
        # content of tests/subpackage/conftest.py
        import pytest
        
        @pytest.fixture(autouse=True)
        def mid(order, b_fix):
            order.append("mid subpackage")

    test_subpackage.py
        # content of tests/subpackage/test_subpackage.py
        import pytest
        
        @pytest.fixture
        def inner(order, mid, a_fix):
            order.append("inner subpackage")
        
        def test_order(order, inner):
            assert order == ["b_fix", "mid subpackage", "a_fix", "inner subpackage"]

如果安装了 plugin_a 提供了fixture a_fix,以及安装了 plugin_b 并提供了fixture b_fix,那么测试搜索fixture的过程如下图所示:

pytest 仅在首先在 tests/ 内部的范围内搜索 a_fixb_fix 之后,才会在插件中搜索它们。

3.1.3 fixture实例化顺序

当 pytest 要执行一个测试时,一旦它知道将要执行哪些fixture,就必须确定这些fixture的执行顺序。为此,它考虑了三个因素:

  1. 范围
  2. 依赖关系
  3. 自动使用

fixture或测试的名称、它们被定义的位置、它们被定义的顺序以及请求fixture的顺序对执行顺序没有影响,除非是巧合。虽然 pytest 会尽力确保这些巧合在每次运行中保持一致,但这并不是可以依赖的东西。如果你想控制顺序,最安全的做法是依赖这三件事,并确保依赖关系明确建立。

范围较大的fixture先执行

在一个函数请求fixture时,范围较大的fixture(如 session)会在范围较小的fixture(如 function 或 class)之前执行。

以下是一个示例:

python 复制代码
from __future__ import annotations

import pytest

@pytest.fixture(scope="session")
def order():
    return []

@pytest.fixture
def func(order):
    order.append("function")

@pytest.fixture(scope="class")
def cls(order):
    order.append("class")

@pytest.fixture(scope="module")
def mod(order):
    order.append("module")

@pytest.fixture(scope="package")
def pack(order):
    order.append("package")

@pytest.fixture(scope="session")
def sess(order):
    order.append("session")

class TestClass:
    def test_order(self, func, cls, mod, pack, sess, order):
        assert order == ["session", "package", "module", "class", "function"]

该测试将通过,因为范围较大的fixture首先执行。

执行顺序如下:

相同顺序的fixture基于依赖关系执行

当一个fixture请求另一个fixture时,另一个fixture会先执行。因此,如果fixture a 请求fixture b,则 b 会先执行,因为 a 依赖于 b 并且没有 b 就无法运行。即使 a 不需要 b 的结果,它仍然可以请求 b 来确保 ba 之后执行。

例如:

python 复制代码
from __future__ import annotations

import pytest

@pytest.fixture
def order():
    return []

@pytest.fixture
def a(order):
    order.append("a")

@pytest.fixture
def b(a, order):
    order.append("b")

@pytest.fixture
def c(b, order):
    order.append("c")

@pytest.fixture
def d(c, b, order):
    order.append("d")

@pytest.fixture
def e(d, b, order):
    order.append("e")

@pytest.fixture
def f(e, order):
    order.append("f")

@pytest.fixture
def g(f, c, order):
    order.append("g")

def test_order(g, order):
    assert order == ["a", "b", "c", "d", "e", "f", "g"]

如果我们绘制出每个fixture依赖于什么,我们会得到这样的结构:

每个fixture提供的规则(关于每个fixture必须跟在哪个fixture之后)足够全面,可以简化为以下内容:

必须通过这些请求提供足够的信息,以便pytest能够弄清楚一个清晰、线性的依赖链,从而得出给定测试的操作顺序。如果有任何含糊不清的地方,且操作顺序可以有多种解释,你应该假设pytest可能在任何时刻选择那些解释中的任何一种。

例如,如果d没有请求c,即图表将看起来像这样:

因为除了g之外没有其他东西请求c,并且g也请求f,现在不清楚c是否应该排在f、e或d之前/之后。为c设置的唯一规则是它必须在b之后执行并在g之前执行。

在这种情况中,pytest不知道c应该放在哪里,所以应该假设它可以放在g和b之间的任何位置。

这并不一定不好,但这是需要记住的一点。如果它们执行的顺序可能会影响测试目标的行为,或者可能以其他方式影响测试的结果,那么应该以一种允许pytest线性化/"简化"该顺序的方式明确地定义这个顺序。

自动使用fixture

自动使用fixture在其作用域内首先执行。假设自动使用fixture适用于所有可能引用它们的测试,因此在该作用域内的其他fixture之前执行。由自动使用fixture请求的fixture实际上对于实际自动使用fixture适用的测试本身也变为自动使用fixture。因此,如果fixture a 是自动使用的,并且fixture b 不是,但是fixture a 请求fixture b,那么fixture b 实际上也将是自动使用的,但仅对于 a 适用的测试。

在最后一个示例中,如果 a 没有请求 c,则图形变得不清楚。但如果 c 是自动使用的,那么 ba 实际上也将是自动使用的,因为 c 依赖于它们。因此,它们将在该作用域内的非自动使用fixture之上移动。

因此,如果测试文件看起来像这样:

python 复制代码
from __future__ import annotations

import pytest

@pytest.fixture
def order():
    return []

@pytest.fixture
def a(order):
    order.append("a")

@pytest.fixture
def b(a, order):
    order.append("b")

@pytest.fixture(autouse=True)
def c(b, order):
    order.append("c")

@pytest.fixture
def d(c, b, order):
    order.append("d")

@pytest.fixture
def e(d, b, order):
    order.append("e")

@pytest.fixture
def f(e, order):
    order.append("f")

@pytest.fixture
def g(f, c, order):
    order.append("g")

def test_order_and_g(g, order):
    assert order == ["a", "b", "c", "d", "e", "f", "g"]

图形将如下所示:

因为现在c可以放在图表中的d之上,pytest可以再次将图表线性化为这样:

在这个例子中,c使得b和a也成为了实际上自动使用的fixture。

不过,使用自动使用时要小心,因为自动使用的fixture会自动为每个可以到达它的测试执行,即使它们没有请求它。例如,考虑这个文件:

复制代码
from __future__ import annotations
import pytest

@pytest.fixture(scope="class")
def order():
	return []

@pytest.fixture(scope="class", autouse=True)
def c1(order):
	order.append("c1")


@pytest.fixture(scope="class")
def c2(order):
	order.append("c2")


@pytest.fixture(scope="class")
def c3(order, c1):
	order.append("c3")


class TestClassWithC1Request:
	def test_order(self, order, c1, c3):
		assert order == ["c1", "c3"]


class TestClassWithoutC1Request:
	def test_order(self, order, c2):
		assert order == ["c1", "c2"]

尽管TestClassWithoutC1Request中的任何东西都没有请求c1,但它仍然会为其内部的测试执行:

仅仅因为一个自动使用的fixture请求了一个非自动使用的fixture,并不意味着该非自动使用的fixture在所有它可以应用的上下文中都变成了自动使用的。它只在实际的自动使用fixture(请求了非自动使用fixture的那个)可以应用的上下文中有效地变成了自动使用的。

示例代码:

python 复制代码
from __future__ import annotations
import pytest

@pytest.fixture
def order():
    return []

@pytest.fixture
def c1(order):
    order.append("c1")

@pytest.fixture
def c2(order):
    order.append("c2")

class TestClassWithAutouse:
    @pytest.fixture(autouse=True)
    def c3(self, order, c2):
        order.append("c3")

    def test_req(self, order, c1):
        assert order == ["c2", "c3", "c1"]

    def test_no_req(self, order):
        assert order == ["c2", "c3"]

class TestClassWithoutAutouse:
    def test_req(self, order, c1):
        assert order == ["c1"]

    def test_no_req(self, order):
        assert order == []

这可以归结为如下:

对于TestClassWithAutouse内的test_req和test_no_req,c3有效地使得c2成为一个自动使用的fixture,这就是为什么即使没有被请求,c2和c3也会为两个测试执行,以及为什么在test_req中c2和c3会在c1之前执行。

如果这使c2成为一个真正的自动使用fixture,那么c2也会为TestClassWithoutAutouse内的测试执行,因为如果它们愿意,这些测试可以引用c2。但实际上并没有,因为从TestClassWithoutAutouse测试的角度来看,由于它们看不到c3,所以c2不是一个自动使用的fixture。

3.2 Pytest 插件列表

以下是 PyPI 上可用的 pytest 插件的自动化编译。它包括名称以 pytest-pytest_ 开头的 PyPI 项目,以及一些手动选择的项目。被分类为不活跃的包被排除在外。有关此列表生成方式的详细信息,请参阅更新脚本。

警告

请注意,此列表不是一个经过策划的项目集合,也没有经过系统的审查过程。它纯粹是一个信息资源,旨在帮助发现 pytest 件。请不要假定 pytest 项目或其开发人员的任何认可,并且在将这些插件纳入自己的项目之前,始终进行自己的质量评估。

此列表包含 1583 个插件。

详细插件列表请参考官网

3.3 配置

3.3.1 命令行选项和配置文件设置

你可以通过使用通用帮助选项来获取命令行选项和 INI 样式配置文件中的值的帮助:

bash 复制代码
pytest -h  # 印选项和配置文件设置

这将显示由已安装插件注册的命令行和配置文件设置。

3.3.2 配置文件格式

许多 pytest 设置可以在配置文件中设置,通常位于仓库的根目录。

pytest 支持的配置文件的一个快速示例:

pytest.ini

pytest.ini 文件优先于其他文件,即使为空也是如此。

或者,可以使用隐藏版本 .pytest.ini

ini 复制代码
# pytest.ini 或 .pytest.ini
[pytest]
minversion = 6.0
addopts = -ra -q
testpaths = 
    tests
    integration
pyproject.toml

在版本 6.0 中添加。

pyproject.toml 包含 tool.pytest.ini_options 时,会考虑其配置。

toml 复制代码
# pyproject.toml
[tool.pytest.ini_options]
minversion = "6.0"
addopts = "-ra -q"
testpaths = [
    "tests",
    "integration",
]

注意

  • 有人可能会想知道为什么是 [tool.pytest.ini_options] 而不是 [tool.pytest],就像其他工具一样。
  • 原因是 pytest 团队打算在未来充分利用丰富的 TOML 数据格式进行配置,保留 [tool.pytest] 用于此目的。现在,ini_options 被用作现有 .ini 配置系统和未来配置格式之间的桥梁。
tox.ini

tox.ini 文件是 tox 项目的配置文件,如果它们有一个 [pytest] 分,也可以用于保存 pytest 置。

ini 复制代码
# tox.ini
[pytest]
minversion = 6.0
addopts = -ra -q
testpaths =
    tests
    integration
setup.cfg

setup.cfg 文件是通用的配置文件,最初由 distutils(现已废弃)和 setuptools 使用,如果它们有一个 [tool:pytest] 部分,也可以用于保存 pytest 配置。

ini 复制代码
# setup.cfg
[tool:pytest]
minversion = 6.0
addopts = -ra -q
testpaths =
    tests
    integration

警告

除非是非常简单的使用情况,否则不建议使用 setup.cfg.cfg 文件使用与 pytest.initox.ini 不同的解析器,这可能会导致难以追踪的问题。如果可能,建议使用后者文件或 pyproject.toml 来保存 pytest 配置。

3.3.3 初始化:确定 rootdir 和 configfile

pytest 为每次测试运行确定一个 rootdir,这取决于命令行参数(指定的测试文件、路径)和配置文件的存在。确定的 rootdir 和 configfile 会在 pytest 动时作为 pytest 标头的一部分打印出来。

以下是 pytest 使用 rootdir 的摘要:

  • 在收集期间构建 nodeids;每个测试被分配一个唯一的 nodeid,该 nodeid 以 rootdir 为根,并考虑完整的路径、类名、函数名和参数化(如果有)。
  • 被插件用作存储项目/测试运行特定信息的稳定位置;例如,内部缓存插件在 rootdir 中创建 .pytest_cache 目录来存储其跨测试运行状态。

rootdir 不用于修改 sys.path 或 PYTHONPATH 或影响模块的导入。有关详细信息,请参阅 pytest 导入机制和 sys.path/PYTHONPATH。

--rootdir=path 命令行选项可以用来强制指定目录。请注意,与其他命令行选项不同,--rootdir 不能与 pytest.ini 中的 addopts 一起使用,因为 rootdir 用于查找 pytest.ini。

查找 rootdir

以下是根据 args 找到 rootdir 的算法:

  • 如果在命令行中传递了 -c,则将其用作配置文件,并将其目录用作 rootdir。
  • 确定指定的 args 中被识别为存在于文件系统中的路径的共同祖先目录。如果没有找到这样的路径,则将共同祖先目录设置为当前工作目录。
  • 在祖先目录及其上级目录中查找 pytest.ini、pyproject.toml、tox.ini 和 setup.cfg 文件。如果匹配到一个,则该文件成为 configfile,其目录成为 rootdir。
  • 如果没有找到配置文件,则从共同祖先目录向上查找 setup.py 以确定 rootdir。
  • 如果没有找到 setup.py,则在指定的 args 及其上级目录中查找 pytest.ini、pyproject.toml、tox.ini 和 setup.cfg。如果匹配到一个,则该文件成为 configfile,其目录成为 rootdir。
  • 如果没有找到 configfile 且没有传递配置参数,则使用已经确定的共同祖先作为根目录。这允许在非包结构中使用 pytest,这些结构没有任何特定的配置文件。
  • 如果没有给定 args,则 pytest 在当前工作目录下收集测试,并从那里开始确定 rootdir。

文件仅在以下情况下匹配配置:

  • pytest.ini:始终匹配并优先级最高,即使为空。
  • pyproject.toml:包含 [tool.pytest.ini_options]
  • tox.ini:包含 [pytest] 分。
  • setup.cfg:包含 [tool:pytest] 部分。

最后,如果未找到其他匹配项,则 pyproject.toml 文件将被视为 configfile,即使它不包含 [tool.pytest.ini_options] 表(这是在 8.1 版本中添加的)。

文件按上述顺序考虑。来自多个 configfiles 选者的选项永远不会合并------第一个匹配项获胜。

配置文件还确定了 rootpath 的值。

Config 对象(通过挂钩或通过 pytestconfig 件访问)将随后携带这些属性:

  • config.rootpath: 确定的根目录,保证存在。它用作构造测试地址("nodeids")的参考目录,并且也可以被插件用于存储每个测试运行的信息。
  • config.inipath: 定的配置文件,可能是 None(由于历史原因命名为 inipath)。

在版本 6.1 中添加:config.rootpathconfig.inipath 属性。它们是旧版 config.rootdirconfig.inifilepathlib.Path 本,后者具有类型 py.path.local,并且仍然存在以保持向后兼容性。

示例:

bash 复制代码
pytest path/to/testdir path/other/

这将确定共同祖先为 path,然后按以下顺序检查配置文件:

plaintext 复制代码
# 首先查找 pytest.ini 文件
path/pytest.ini
path/pyproject.toml  # 必须包含 [tool.pytest.ini_options] 表格以匹配
path/tox.ini         # 必须包含 [pytest] 部分以匹配
path/setup.cfg       # 必须包含 [tool:pytest] 部分以匹配
pytest.ini           # ... 一直到根目录

# 在查找 setup.py
path/setup.py
setup.py             # ... 一直到根目录

警告

自定义 pytest 插件命令行参数可能包括路径,例如 pytest --log-output ../../test.log args。此时 args 是必需的,否则 pytest 使用 test.log 文件夹来确定 rootdir(参见 #1435)。使用点 . 引用当前工作目录也是可能的。

3.3.4 内置配置文件选项

有关完整选项列表,请参阅本文档其他部分。

3.3.5 语法高亮主题自定义

pytest 使用的语法高亮主题可以通过两个环境变量进行自定义:

  • PYTEST_THEME 设置要使用的 pygment 格。
  • PYTEST_THEME_MODE 将此风格设置为 light 或 dark。

3.4 API 参考

本页包含 pytest API 的完整参考。

3.4.1 常量

pytest.version

当前 pytest 版本,作为字符串:

python 复制代码
>>> import pytest
>>> pytest.__version__
'7.0.0'
pytest.version_tuple

在版本 7.0 中添加。当前 pytest 版本,作为元组:

python 复制代码
>>> import pytest
>>> pytest.version_tuple
(7, 0, 0)

对于预发布版,最后一个组件将是一个带有预发布版本的字符串:

python 复制代码
>>> import pytest
>>> pytest.version_tuple
(7, 0, 'rc1')

3.4.2 函数

pytest.approx

approx(expected, rel=None, abs=None, nan_ok=False)

  • 断言两个数字(或两个有序的数字序列)在某个容差范围内相等。

  • 由于浮点算术问题和限制,我们直观上期望相等的数字并不总是如此:

    python 复制代码
    >>> 0.1 + 0.2 == 0.3
    False
  • 这个问题在编写测试时经常遇到,例如确保浮点值是你期望的值。处理这个问题的一种方法是断言两个浮点数在适当的容差范围内相等:

    python 复制代码
    >>> abs((0.1 + 0.2) - 0.3) < 1e-6
    True
  • 然而,像这样的比较写起来很繁琐且难以理解。此外,像上面那样的绝对比较通常不被鼓励,因为没有一个适用于所有情况的容差。1e-6 对于大约 1 的数字来说是好的,但对于非常大的数字来说太小,对于非常小的数字来说太大。最好将容差表示为预期值的分数,但像这样的相对比较更难正确简洁地写出。

  • approx 类使用尽可能直观的语法进行浮点数比较:

    python 复制代码
    >>> from pytest import approx
    >>> 0.1 + 0.2 == approx(0.3)
    True
  • 相同的语法也适用于有序的数字序列:

    python 复制代码
    >>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
    True
  • numpy 数组:

    python 复制代码
    >>> import numpy as np
    >>> np.array([0.1, 0.2]) + np.array([0.2, 0.4]) == approx(np.array([0.3, 0.6]))
    True
  • 以及 numpy 数组与标量的比较:

    python 复制代码
    >>> import numpy as np
    >>> np.array([0.1, 0.2]) + np.array([0.2, 0.1]) == approx(0.3)
    True
  • 仅支持有序序列,因为 approx 要在无歧义的情况下推断序列的相对位置。这意味着集合和其他无序序列不被支持。

  • 最后,字典值也可以进行比较:

    python 复制代码
    >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
    True
  • 如果两个映射具有相同的键,并且它们各自的值符合预期的容差,则比较结果为真。

  • 容差

  • 默认情况下,approx 认为数字在其预期值的相对容差范围内(即 1e-6,即百万分之一)是相等的。如果预期值为 0.0,则这种处理会导致令人惊讶的结果,因为除了 0.0 本身之外,没有任何东西与 0.0 对接近。为了更少地让人感到意外,approx 考虑了在绝对容差范围内(即 1e-12)的数字是相等的。无穷大和 NaN 是特殊情况。无论相对容差如何,无穷大只被认为等于自身。NaN 默认不被认为等于任何东西,但你可以通过将 nan_ok 参数设置为 True 来使其等于自身。(这是为了方便比较使用 NaN 表示"无数据"的数组。)

  • 相对容差和绝对容差都可以通过向 approx 造函数传递参数来更改:

    python 复制代码
    >>> 1.0001 == approx(1)
    False
    >>> 1.0001 == approx(1, rel=1e-3)
    True
    >>> 1.0001 == approx(1, abs=1e-3)
    True
  • 如果你指定了 abs 但没有指定 rel,则比较将完全不考虑相对容差。换句话说,即使两个数字在默认的相对容差 1e-6 范围内,但如果它们超过了指定的绝对容差,仍然会被认为不相等。如果你同时指定了 absrel,则只要满足任一容差条件,这两个数字就会被认为是相等的:

    python 复制代码
    >>> 1 + 1e-8 == approx(1)
    True
    >>> 1 + 1e-8 == approx(1, abs=1e-12)
    False
    >>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12)
    True
  • 你还可以使用 approx 来比较非数值类型,或者包含非数值类型的字典和序列,在这种情况下,它会退回到严格的相等性比较。这对于比较可能包含可选值的字典和序列非常有用:

    python 复制代码
    >>> {"required": 1.0000005, "optional": None} == approx({"required": 1, "optional": None})
    True
    >>> [None, 1.0000005] == approx([None, 1])
    True
    >>> ["foo", 1.0000005] == approx([None, 1])
    False
  • 如果你正在考虑使用 approx,那么你可能想知道它与其他比较浮点数的好方法相比如何。所有这些算法都基于相对和绝对容差,并且在大多数情况下应该一致,但它们确实存在有意义的差异:

    • math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0):如果相对于 a 或 b 的相对容差满足,或者绝对容差满足,则为 True。因为相对容差是相对于 a 和 b 算的,所以这个测试是对称的(即 a 和 b 不是"参考值")。如果你想与 0.0 进行比较,必须指定一个绝对容差,因为默认没有容差。更多信息:math.isclose()
    • numpy.isclose(a, b, rtol=1e-5, atol=1e-8):如果 a 和 b 之间的差小于相对于 b 的相对容差和绝对容差之和,则为 True。因为相对容差仅相对于 b 算,所以这个测试是非对称的,你可以将 b 视为参考值。支持序列比较由 numpy.allclose() 提供。更多信息:numpy.isclose
    • unittest.TestCase.assertAlmostEqual(a, b):如果 a 和 b 在绝对容差 1e-7 以内,则为 True。不考虑相对容差,因此该函数不适合非常大或非常小的数字。此外,它仅在 unittest.TestCase 的子类中可用,并且由于不遵循 PEP 8 而显得丑陋。更多信息:unittest.TestCase.assertAlmostEqual()
    • a == pytest.approx(b, rel=1e-6, abs=1e-12):如果相对于 b 的相对容差满足,或者绝对容差满足,则为 True。因为相对容差仅相对于 b 计算,所以这个测试是非对称的,你可以将 b 视为参考值。在特殊情况下,如果你显式指定了绝对容差但没有指定相对容差,则只考虑绝对容差。
  • 注意

    • approx 可以处理 numpy 数组,但我们建议在需要比较、NaN 或 ULP 基于容差的支持时使用 Test 支持(numpy.testing)中的专门测试辅助工具。

    • 要使用正则表达式匹配字符串,可以使用 re_assert 包中的 Matches

  • 警告

    • 版本 3.2 中更改:

    • 为了避免不一致的行为,对于 >、>=、< 和 <= 比较,会引发 TypeError。下面的例子说明了这个问题:

      python 复制代码
      assert approx(0.1) > 0.1 + 1e-10  # 调用 approx(0.1).__gt__(0.1 + 1e-10)
      assert 0.1 + 1e-10 > approx(0.1)  # 调用 approx(0.1).__lt__(0.1 + 1e-10)
    • 在第二个例子中,期望调用 approx(0.1).__le__(0.1 + 1e-10)。但实际上,调用了 approx(0.1).__lt__(0.1 + 1e-10) 行比较。这是因为丰富的比较调用层次遵循固定的行为。更多信息:object.__ge__()

  • 版本 3.7.1 中更改:当遇到非数值类型的字典值或序列元素时,approx 会引发 TypeError

  • 版本 6.1.0 中更改:对于非数值类型,approx 回退到严格的相等性比较,而不是引发 TypeError

pytest.fail

fail(reason[, pytrace=True])

  • 显式地使正在执行的测试失败,并显示给定的消息。
  • 参数:
    • reason (str) -- 显示给用户作为失败原因的消息。
    • pytrace (bool) -- 如果为 False,msg 代表完整的失败信息,不会报告 Python 踪回溯。
  • Raises:
    • pytest.fail.Exception -- 由 pytest.fail() 引发的异常。

class pytest.fail.Exception

  • pytest.fail() 引发的异常。
pytest.skip

skip(reason[, allow_module_level=False])

  • 跳过正在执行的测试,并显示给定的消息。

  • 此函数仅应在测试期间(设置、调用或清理)或在收集时使用 allow_module_level 标志时调用。此函数也可以在 doctests 中调用。

  • 参数:

    • reason (str) -- 显示给用户作为跳过原因的消息。
    • allow_module_level (bool) -- 允许此函数在模块级别调用。在模块级别引发跳过异常将停止模块的执行并阻止模块中所有测试的收集,即使这些测试在 skip 用之前定义。默认为 False。
  • Raises:

    • pytest.skip.Exception -- 由 pytest.skip() 引发的异常。
  • 注意

    • 最好尽可能使用 pytest.mark.skipif 标记来声明在某些条件下(如平台不匹配或依赖项问题)跳过测试。同样,使用 # doctest: +SKIP 指令(参见 doctest.SKIP)静态跳过 doctest。

class pytest.skip.Exception

  • pytest.skip() 引发的异常。
pytest.importorskip

importorskip(modname, minversion=None, reason=None, *, exc_type=None)

  • 导入并返回请求的模块 modname,如果无法导入该模块,则跳过当前测试。

  • 参数:

    • modname (str) -- 导入的模块名称。

    • minversion (str / None) -- 如果给定,导入模块的 _version_ 属性必须至少为这个最小版本,否则测试仍然被跳过。

    • reason (str / None) -- 如果给定,当模块无法导入时,此原因将作为消息显示给用户。

    • exc_type (type[ImportError] / None) -- 为了跳过模块而应捕获的异常。必须是 ImportError 或其子类。

      • 如果模块可以导入但引发 ImportError,pytest 将向用户发出警告,因为用户通常期望找不到模块(这将引发 ModuleNotFoundError 不是 ImportError)。

      • 此警告可以通过显式传递 exc_type=ImportError 来抑制。

      • 有关 ImportError 的默认行为,请参阅 pytest.importorskip

  • 返回

    • 导入的模块。这应该分配给其规范名称。
  • Raises:

    • pytest.skip.Exception -- 如果模块无法导入。
  • 返回类型

    • Any
  • 示例:

    python 复制代码
    docutils = pytest.importorskip("docutils")
  • 在版本 8.2 中添加:exc_type 参数。

pytest.xfail

xfail(reason="")

  • 强制使正在执行的测试或设置函数失败,并给出指定的原因。

  • 此函数仅应在测试期间(设置、调用或清理)调用。

  • 使用 xfail() 后不再执行其他代码(它通过内部引发异常来实现)。

  • 参数:

    • reason (str) -- 显示给用户作为失败原因的消息。
  • 注意

    • 可能的话,最好使用 pytest.mark.xfail 标记来声明测试在某些条件下(如已知错误或缺失功能)预期失败。
  • Raises:

    • pytest.xfail.Exception -- 引发的异常。

class pytest.xfail.Exception

  • pytest.xfail() 发的异常。
pytest.exit

exit(reason[, returncode=None])

  • 退出测试过程。

  • 参数:

    • reason (str) -- 作为退出 pytest 的原因显示的消息。reason 有一个默认值,仅因为 msg 已被弃用。
    • returncode (int / None) -- 退出 pytest 时使用的返回代码。None 表示与 0(无错误)相同,与 sys.exit() 同。
  • Raises:

    • pytest.exit.Exception -- 引发的异常。

class pytest.exit.Exception

  • pytest.exit() 引发的异常。
pytest.main

main(args=None, plugins=None)

  • 执行进程内测试运行。

  • 参数:

    • args (list[str] / PathLike[str] / None) -- 命令行参数列表。如果为 None 或未给出,默认从进程命令行 (sys.argv) 读取参数。
    • plugins (Sequence[str / object] / None) -- 在初始化期间自动注册的插件对象列表。
  • 返回

    • 一个退出代码。
  • 返回类型

    • int | ExitCode
pytest.param

param(*values[, id][, marks])

  • pytest.mark.parametrize 调用或参数化fixture中指定一个参数。

    python 复制代码
    @pyest.mark.parametrize(
        "test_input,expected",
        [
            ("3+5", 8),
            pytest.param("6*9", 42, marks=pytest.mark.xfail),
        ],
    )
    def test_eval(test_input, expected):
        assert eval(test_input) == expected
  • 参数:

    • values (object) -- 参数集的值变量参数,按顺序排列。
    • marks (MarkDecorator / Collection[MarkDecorator / Mark]) -- 应用于此参数集的一个标记或标记列表。pytest.mark.usefixtures 不能通过此参数添加。
    • id (str / None) -- 要分配给此参数集的 ID。
pytest.raises

with raises(expected_exception: type[E] | tuple[type[E], ...], *, match: str | Pattern[str] | None = ...) → RaisesContext[E] as excinfo
with raises(expected_exception: type[E] | tuple[type[E], ...], func: Callable[..., Any], *args: Any, **kwargs: Any) → ExceptionInfo[E] as excinfo

  • 断言代码块/函数调用会引发异常类型,或其子类之一。

  • 参数:

    • expected_exception -- 期的异常类型,或多个可能的异常类型的元组。请注意,传递的异常的子类也将匹配。

    • match (str / re.Pattern[str] / None) -- 如果指定,一个包含正则表达式的字符串,或一个正则表达式对象,用于测试异常的字符串表示及其 PEP 678 notes 使用 re.search()

      • 要匹配可能包含特殊字符的字面字符串,可以首先使用 re.escape() 对模式进行转义。

      • (这仅在将 pytest.raises 用作上下文管理器时使用,并且在其他情况下传递给函数。当将 pytest.raises 用作函数时,可以使用:pytest.raises(Exc, func, match="passed on").match("my pattern")。)

  • 使用 pytest.raises 作为上下文管理器,它将捕获给定类型的异常,或其任何子类:

    python 复制代码
    >>> import pytest
    >>> with pytest.raises(ZeroDivisionError):
    ...     1/0
  • 如果代码块没有引发预期的异常(例如上面的 ZeroDivisionError),或者根本没有引发异常,检查将失败。

  • 你还可以使用关键字参数 match 来断言异常与文本或正则表达式匹配:

    python 复制代码
    >>> with pytest.raises(ValueError, match='must be 0 or None'):
    ...     raise ValueError("value must be 0 or None")
    >>> with pytest.raises(ValueError, match=r'must be \d+$'):
    ...     raise ValueError("value must be 42")
  • match 参数搜索格式化的异常字符串,其中包含任何 PEP-678 notes

    python 复制代码
    >>> with pytest.raises(ValueError, match=r"had a note added"):
    ...     e = ValueError("value must be 42")
    ...     e.add_note("had a note added")
    ...     raise e
  • 上下文管理器生成一个 ExceptionInfo 对象,可用于检查捕获异常的详细信息:

    python 复制代码
    >>> with pytest.raises(ValueError) as exc_info:
    ...     raise ValueError("value must be 42")
    >>> assert exc_info.type is ValueError
    >>> assert exc_info.value.args[0] == "value must be 42"
  • 警告

    • 鉴于 pytest.raises 匹配子类,请小心使用它来匹配 Exception,如下所示:

      python 复制代码
      # 注意,这将捕获任何引发的异常。
      with pytest.raises(Exception):
          some_function()
    • 因为 Exception 是几乎所有异常的基本类,很容易通过这种方式隐藏真正的错误,用户编写此代码时期望捕获特定异常,但由于重构过程中引入的错误,其他异常被引发。

    • 除非确定确实想捕获任何引发的异常,否则避免使用 pytest.raises 捕获 Exception

  • 注意

    • 当使用 pytest.raises 作为上下文管理器时,需要注意正常上下文管理器规则适用,并且引发的异常必须是上下文管理器作用域中的最后一行。在此上下文管理器作用域之后的代码行将不会被执行。例如:

      python 复制代码
      >>> value = 15
      >>> with pytest.raises(ValueError) as exc_info:
      ...     if value > 10:
      ...         raise ValueError("value must be <= 10")
      ...     assert exc_info.type is ValueError  # 这一行将不会执行。
    • 相反,必须采取以下方法(注意作用域的不同):

      python 复制代码
      >>> with pytest.raises(ValueError) as exc_info:
      ...     if value > 10:
      ...         raise ValueError("value must be <= 10")
      ...     assert exc_info.type is ValueError
  • Using with pytest.mark.parametrize

    • 当使用 pytest.mark.parametrize 时,可以参数化测试,使得某些运行引发异常而其他运行不引发。

    • 参见 Parametrizing conditional raising 以获取示例。

  • Legacy form

    • 可以通过传递一个待调用的 lambda 函数来指定可调用对象:

      python 复制代码
      >>> raises(ZeroDivisionError, lambda: 1/0)
      <ExceptionInfo ...>
    • 或者可以指定具有参数的任意可调用对象:

      python 复制代码
      >>> def f(x): return 1/x
      ...
      >>> raises(ZeroDivisionError, f, 0)
      <ExceptionInfo ...>
      >>> raises(ZeroDivisionError, f, x=0)
      <ExceptionInfo ...>
    • 上述形式完全受支持,但不建议用于新代码,因为上下文管理器形式被认为更具可读性且更少出错。

  • 注意

    • 类似于 Python 中捕获的异常对象,显式清除返回的 ExceptionInfo 对象的本地引用可以帮助 Python 解释器加快其垃圾回收。

    • 清除这些引用会打破一个引用循环 (ExceptionInfo -> 获的异常 -> 异常栈 -> 当前帧栈 -> 局部变量 -> ExceptionInfo),这使得 Python 保持从该循环中引用的所有对象(包括当前帧中的所有局部变量)存活,直到下一次循环垃圾回收运行。有关 try 语句的更详细信息可以在官方 Python 文档中找到。

pytest.deprecated_call

with deprecated_call(*, match: str | Pattern[str] | None = ...) → WarningsRecorder
with deprecated_call(func: Callable[..., T], *args: Any, **kwargs: Any) → T

  • 断言代码会生成 DeprecationWarningPendingDeprecationWarningFutureWarning

  • 此函数可以用作上下文管理器:

    python 复制代码
    >>> import warnings
    >>> def api_call_v2():
    ...     warnings.warn('use v3 of this api', DeprecationWarning)
    ...     return 200
    ...
    >>> import pytest
    >>> with pytest.deprecated_call():
    ...     assert api_call_v2() == 200
  • 它也可以通过传递一个函数和 *args**kwargs 来使用,在这种情况下,它将确保调用 func(*args, **kwargs) 会产生上述警告类型之一。返回值是函数的返回值。

  • 在上下文管理器形式中,可以使用关键字参数 match 言警告与文本或正则表达式匹配。

  • 上下文管理器生成一个 warnings.WarningMessage 对象列表,每个引发的警告一个对象。

pytest.register_assert_rewrite

register_assert_rewrite(*names)

  • 用于在导入时重写一个或多个模块名称。
  • 此函数将确保该模块或包内的所有模块的断言语句被重写。因此,你应该确保在实际导入模块之前调用它,通常在你的 __init__.py 文件中,如果你正在使用一个插件。
  • 参数:
    • names (str) - 注册的模块名称。
pytest.warns

with warns(expected_warning: type[Warning] | tuple[type[Warning], ...] = <class 'Warning'>, *, match: str |~re.Pattern[str] | None = None)→ WarningsChecker
with warns(expected_warning: type[Warning] | tuple[type[Warning], ...], func: Callable[[...], T], *args: Any, **kwargs: Any)→T

  • pytest.warns 用于断言代码会引发特定类别的警告。

  • 具体来说,参数 expected_warning 可以是一个警告类或警告类的元组,并且 with 块中的代码必须至少发出一个该类或这些类的警告。

  • 此帮助程序会产生一个警告列表,每个发出的警告都有一个 WarningMessage 对象(无论是否为预期警告)。自 pytest 8.0 本起,当上下文关闭时,未匹配的警告也会重新发出。

  • 此函数可以用作上下文管理器:

    python 复制代码
    import pytest
    with pytest.warns(RuntimeWarning):
        warnings.warn("my warning", RuntimeWarning)
  • 在上下文管理器形式中,你可以使用关键字参数 match 断言警告与文本或正则表达式匹配:

    python 复制代码
    with pytest.warns(UserWarning, match='must be 0 or None'):
        warnings.warn("value must be 0 or None", UserWarning)
    
    with pytest.warns(UserWarning, match=r'must be \d+$'):
        warnings.warn("value must be 42", UserWarning)
    
    with pytest.warns(UserWarning):
        # catch re-emitted warning
        with pytest.warns(UserWarning, match=r'must be \d+$'):
            warnings.warn("this is not here", UserWarning)
  • 如果未发出预期的警告,将出现以下错误:

    python 复制代码
    Traceback (most recent call last):
    ...
    Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...
  • Using with pytest.mark.parametrize

    • 当使用 pytest.mark.parametrize 时,可以参数化测试,使得某些运行会发出警告而其他运行不会。

    • 这可以通过与异常相同的方式实现,参见 Parametrizing conditional raising 以获取示例。

pytest.freeze_includes

Tutorial: Freezing pytest

freeze_includes()

  • 返回一个列表,包含 pytest 使用的模块名称,这些模块应该被 cx_freeze 包含。

下一章

相关推荐
m0_632482506 小时前
Jenkins + Pytest +allure接口自动化测试配置与操作
jenkins·集成测试·pytest·jenkins配置
哎呀呦呵6 小时前
pytest基本使用
python·pytest
阿关@6 小时前
Vscode中Python无法将pip/pytest”项识别为 cmdlet、函数、脚本文件或可运行程序的名称
vscode·python·pip
Kristen_YXQDN6 小时前
PyCharm 中 pytest 运行 python 测试文件报错:D:\Python_file\.venv\Scripts\python.exe: No module named pytest
运维·开发语言·python·pycharm·pytest
Low--Key6 小时前
pytest框架快速入门
python·自动化·pytest
IMPYLH6 小时前
Lua 的 Debug(调试) 模块
开发语言·笔记·python·单元测试·lua·fastapi
姜西西_6 小时前
自动化测试框架pytest之fixture
android·java·pytest
普通网友6 小时前
更优雅的测试:Pytest框架入门
jvm·数据库·python