Fixtures 参考

Fixtures reference

See also

关于 fixtures

内置 fixtures

Built-in fixtures

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

capfd

捕获文件描述符 12 的文本输出。

capfdbinary

捕获文件描述符 12 的字节输出。

caplog

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

capsys

捕获 sys.stdoutsys.stderr 的文本输出。

capsysbinary

捕获 sys.stdoutsys.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 替代。

Fixture 可用性

Fixture availability

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

类似地,只有当测试在同一个作用域内时,它才能受到autouse fixtures 的影响(见 Autouse Fixture 在其范围内首先执行 )。

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

例如,这里有一个测试文件,其中一个 fixtures ( outer )请求了一个在不同作用域内定义的 fixtures ( 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"]

从测试的角度来看,它们没有问题看到每个它们依赖的 fixtures :

../_images/test_fixtures_request_different_scope.svg

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

Note

fixtures 的定义作用域对其实例化顺序没有影响:顺序由 here 中描述的逻辑决定。

conftest.py: 跨多个文件分享 fixtures

conftest.py: sharing fixtures across multiple files

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

你可以拥有多个嵌套的目录/包来包含测试,每个目录可以有自己的 conftest.py 及其 fixtures ,这些 fixtures 将附加到父目录中的 conftest.py 文件提供的 fixtures 上。

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

tests/
    __init__.py

    conftest.py
        # tests/conftest.py 的内容
        import pytest

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

        @pytest.fixture
        def top(order, innermost):
            order.append("top")

    test_top.py
        # 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
            # tests/subpackage/conftest.py 的内容
            import pytest

            @pytest.fixture
            def mid(order):
                order.append("mid subpackage")

        test_subpackage.py
            # 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 文件中定义的 fixtures 对整个作用域可用。

测试可以向上搜索(跨出一个圆圈)以找到 fixtures ,但不能向下搜索(进入一个圆圈)以继续查找。因此,tests/subpackage/test_subpackage.py::test_order 能够找到在 tests/subpackage/test_subpackage.py 中定义的 innermost fixtures ,但在 tests/test_top.py 中定义的 fixtures 对它不可用,因为它必须下探一个层级(进入一个圆圈)才能找到它。

测试找到的第一个 fixtures 将被使用,因此 :ref:` fixtures 可以被重写 <override fixtures>` 如果你需要更改或扩展特定作用域的 fixtures 功能。

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

来自第三方插件的 fixtures

Fixtures from third-party plugins

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

由于它们是从测试套件结构之外提供的,第三方插件并不像 conftest.py 文件和测试套件中的目录那样提供作用域。因此,pytest 会按照之前解释的方式在作用域之间搜索 fixtures ,只有在最后才会找到在插件中定义的 fixtures 。

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

tests/
    __init__.py

    conftest.py
        # tests/conftest.py 的内容
        import pytest

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

    subpackage/
        __init__.py

        conftest.py
            # tests/subpackage/conftest.py 的内容
            import pytest

            @pytest.fixture(autouse=True)
            def mid(order, b_fix):
                order.append("mid subpackage")

        test_subpackage.py
            # 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 已安装并提供 fixtures a_fix,而 plugin_b 已安装并提供 fixtures b_fix,那么测试查找 fixtures 的过程如下:

../_images/fixture_availability_plugins.svg

pytest 只会在插件中搜索 a_fixb_fix,在此之前会先在 tests/ 内的作用域中查找。

Fixture 实例顺序

Fixture instantiation order

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

  1. 作用域

  2. 依赖关系

  3. 自动使用(autouse)

fixtures 或测试的名称、它们的定义位置、定义顺序以及请求 fixtures 的顺序在执行顺序上没有其他影响,仅仅是巧合。虽然 pytest 会尽量确保这些巧合在每次运行中保持一致,但这并不是应该依赖的因素。如果你想控制执行顺序,最安全的做法是依赖这三个因素,并确保依赖关系明确。

更高范围的 fixtures 将首先执行

Higher-scoped fixtures are executed first

在对 fixtures 的函数请求中,较高作用域的 fixtures (例如 session)会在较低作用域的 fixtures (例如 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"]

测试将会通过,因为较大作用域的 fixtures 优先执行。

执行顺序如下:

../_images/test_fixtures_order_scope.svg

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

Fixtures of the same order execute based on dependencies

当一个 fixtures 请求另一个 fixtures 时,另一个 fixtures 会首先执行。因此,如果 fixtures a 请求 fixtures b, fixtures 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

每个 fixtures 所提供的规则(即每个 fixtures 必须在什么 fixtures 之后执行)是足够全面的,可以简化为:

../_images/test_fixtures_order_dependencies_flat.svg

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

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

../_images/test_fixtures_order_dependencies_unclear.svg

因为除了 g 之外,没有任何东西请求 c,而 g 也请求 f,因此现在不清楚 c 应该在 fe 还是 d 之前/之后执行。对 c 的唯一规则是它必须在 b 之后和 g 之前执行。

在这种情况下,pytest 不知道 c 应该放在什么地方,因此应假设它可以在 gb 之间的任何位置。

这并不一定是坏事,但值得注意。如果它们的执行顺序可能影响测试所针对的行为,或可能以其他方式影响测试结果,则应以允许 pytest 线性化/”扁平化” 该顺序的方式明确定义顺序。

Autouse Fixture 在其范围内首先执行

Autouse fixtures are executed first within their scope

autouse fixtures 假定适用于可以引用它们的每个测试,因此它们会在该范围内的其他 fixtures 之前执行。被 autouse fixtures 请求的 fixtures 在实际上也会成为适用于真实 autouse fixtures 所应用测试的 autouse fixtures 。

所以,如果 fixtures a 是自动使用的,而 fixtures b 不是,但 fixtures a 请求了 fixtures b,那么 fixtures b 在实际上也将成为 autouse fixtures ,但仅适用于 a 适用的测试。

在最后一个例子中,如果 d 没有请求 c,图形会变得不清晰。但如果 c 是自动使用的,那么 ba 实际上也将是自动使用的,因为 c 依赖于它们。因此,它们都将被提升到该范围内的非 autouse fixtures 之上。

所以如果测试文件看起来像这样:

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 fixtures 。

不过,请小心使用 autouse fixtures ,因为 autouse fixtures 将自动为每个能够到达它的测试执行,即使这些测试没有请求它。例如,考虑这个文件:

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 fixtures 请求了一个非 autouse fixtures ,并不意味着这个非 autouse fixtures 会在所有适用的上下文中变为 autouse fixtures 。它仅在真实的 autouse fixtures (请求非 autouse fixtures 的那个)所能应用的上下文中有效地变为 autouse fixtures 。

例如,看看这个测试文件:

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 fixtures ,这就是为什么 c2c3 会在两个测试中执行,尽管没有被请求,以及为什么 c2c3 会在 test_reqc1 之前执行。

如果这使得 c2 成为一个 实际 的 autouse fixtures ,那么 c2 也会为 TestClassWithoutAutouse 中的测试执行,因为它们如果想的话可以引用 c2。但它不会,因为从 TestClassWithoutAutouse 测试的角度看,c2 不是一个 autouse fixtures ,因为它们无法看到 c3