Fixtures 参考

另请参见

关于 fixtures

另请参见

如何使用 fixtures

内置 fixtures

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

capfd

将输出到文件描述符 12 的内容捕获为文本。

capfdbinary

将输出到文件描述符 12 的内容捕获为字节。

caplog

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

capsys

将输出到 sys.stdoutsys.stderr 的内容捕获为文本。

capteesys

以与 capsys 相同的方式捕获,但也会根据 --capture= 传递文本。

capsysbinary

将输出到 sys.stdoutsys.stderr 的内容捕获为字节。

cache

在 pytest 运行期间存储和检索值。

doctest_namespace

提供一个注入到 doctest 命名空间的字典。

monkeypatch

临时修改类、函数、字典、os.environ 和其他对象。

pytestconfig

访问配置值、插件管理器和插件钩子。

subtests

允许在测试函数中声明子测试。

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 替换。

Fixture 可用性

Fixture 的可用性从测试的角度确定。Fixture 仅在测试可以请求的范围内可用。如果 fixture 定义在类内部,则只能由该类内部的测试请求。但如果 fixture 定义在模块的全局范围内,则该模块中的每个测试,即使定义在类内部,也可以请求它。

同样,如果测试与 autouse fixture 定义在同一范围内,则该测试也只能受 autouse fixture 的影响(参见 Autouse fixture 在其范围内首先执行)。

Fixture 还可以请求任何其他 fixture,无论它定义在哪里,只要请求它们的测试能够看到所有相关的 fixture。

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

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

../_images/test_fixtures_request_different_scope.svg

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

注意

Fixture 定义的范围对其实例化顺序没有影响:顺序由 此处 描述的逻辑强制执行。

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

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

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

例如,给定这样的测试文件结构

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"]

范围的边界可以像这样可视化

../_images/fixture_availability.svg

目录成为它们自己的一种范围,在该目录中的 conftest.py 文件中定义的 fixture 在整个范围内可用。

测试被允许向上搜索(走出圈子)寻找 fixture,但绝不能向下(进入圈子)继续搜索。因此,tests/subpackage/test_subpackage.py::test_order 能够找到在 tests/subpackage/test_subpackage.py 中定义的 innermost fixture,但在 tests/test_top.py 中定义的 fixture 对其不可用,因为它必须向下移动一个级别(进入圈子)才能找到它。

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

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

来自第三方插件的 fixture

Fixture 不必以这种结构定义才能供测试使用。它们也可以由已安装的第三方插件提供,许多 pytest 插件就是这样运作的。只要这些插件已安装,它们提供的 fixture 就可以从测试套件中的任何位置请求。

由于它们是从测试套件结构外部提供的,第三方插件并不像 conftest.py 文件和测试套件中的目录那样提供范围。因此,pytest 将如前所述通过范围逐步向外搜索 fixture,只有在 *最后* 才搜索插件中定义的 fixture。

例如,给定以下文件结构

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 的搜索将如下所示

../_images/fixture_availability_plugins.svg

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

Fixture 实例化顺序

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

  1. 范围

  2. 依赖关系

  3. autouse

Fixture 或测试的名称、它们的定义位置、它们的定义顺序以及 fixture 的请求顺序对执行顺序没有影响,除了巧合。虽然 pytest 会努力确保这些巧合在不同运行之间保持一致,但这不应该被依赖。如果您想控制顺序,最安全的方法是依赖这 3 点并确保明确建立依赖关系。

更高范围的 fixture 首先执行

在对 fixture 的函数请求中,更高范围的 fixture(例如 session)在较低范围的 fixture(例如 functionclass)之前执行。

这是一个例子

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 首先执行。

顺序分解如下

../_images/test_fixtures_order_scope.svg

相同顺序的 fixture 根据依赖关系执行

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

例如

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"]

如果我们绘制出什么依赖于什么,我们得到的结果看起来像这样

../_images/test_fixtures_order_dependencies.svg

每个 fixture 提供的规则(关于每个 fixture 必须在哪些 fixture 之后)足够全面,可以将其平铺为这样

../_images/test_fixtures_order_dependencies_flat.svg

必须通过这些请求提供足够的信息,以便 pytest 能够找出清晰的线性依赖链,从而找出给定测试的操作顺序。如果存在任何歧义,并且操作顺序可以有多种解释,则应假定 pytest 可以在任何时候选择其中任何一种解释。

例如,如果 d 没有请求 c,即图表将如下所示

../_images/test_fixtures_order_dependencies_unclear.svg

因为除了 g 之外没有其他东西请求 c,而 g 也请求 f,现在不清楚 c 应该在 fed 之前/之后执行。<为 c 设置的唯一规则是它必须在 b 之后和 g 之前执行。

在这种情况下,pytest 不知道 c 应该放在哪里,所以应该假定它可以在 gb 之间的任何位置。

这不一定是坏事,但需要牢记。如果它们的执行顺序可能影响测试目标行为,或者可能影响测试结果,那么应该以允许 pytest 线性化/“扁平化”该顺序的方式明确定义顺序。

Autouse fixture 在其范围内首先执行

Autouse fixture 被假定适用于所有可以引用它们的测试,因此它们在该范围内的其他 fixture 之前执行。被 autouse fixture 请求的 fixture 对于实际 autouse fixture 所应用的测试来说,实际上也变成了 autouse fixture。

因此,如果 fixture a 是 autouse 的,而 fixture b 不是,但 fixture a 请求了 fixture b,那么 fixture b 实际上也将是 autouse fixture,但仅限于 a 适用的测试。

在上一个例子中,如果 d 没有请求 c,图表就变得不清楚了。但是如果 c 是 autouse 的,那么 ba 实际上也会变成 autouse 的,因为 c 依赖于它们。因此,它们都会在该范围内的非 autouse fixture 之上。

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

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(b, order):
    order.append("d")


@pytest.fixture
def e(d, 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"]

图表将如下所示

../_images/test_fixtures_order_autouse.svg

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

../_images/test_fixtures_order_autouse_flat.svg

在此示例中,c 也使 ba 实际上成为 autouse fixture。

但是要小心使用 autouse,因为 autouse 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,但它仍然为其中的测试执行

../_images/test_fixtures_order_autouse_multiple_scopes.svg

但仅仅因为一个 autouse fixture 请求了一个非 autouse fixture,并不意味着该非 autouse fixture 变成了适用于所有上下文的 autouse fixture。它只在实际的 autouse fixture(请求非 autouse fixture 的那个)可以应用的上下文中实际上变成一个 autouse fixture。

例如,看看这个测试文件

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 == []

它将分解为以下内容

../_images/test_fixtures_order_autouse_temp_effects.svg

对于 TestClassWithAutouse 中的 test_reqtest_no_reqc3 实际上使 c2 成为一个 autouse fixture,这就是为什么 c2c3 为这两个测试执行,尽管没有被请求,以及为什么 c2c3test_reqc1 之前执行。

如果这使得 c2 成为一个 *实际的* autouse fixture,那么 c2 也将为 TestClassWithoutAutouse 中的测试执行,因为如果它们愿意,它们可以引用 c2。但它不会,因为从 TestClassWithoutAutouse 测试的角度来看,c2 不是一个 autouse fixture,因为它们看不到 c3