夹具参考

另请参阅

关于夹具

另请参阅

如何使用夹具

内置夹具

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

capfd

以文本形式捕获输出到文件描述符 12 的内容。

capfdbinary

以字节形式捕获输出到文件描述符 12 的内容。

caplog

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

capsys

以文本形式捕获输出到 sys.stdoutsys.stderr 的内容。

capteesys

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

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

夹具可用性

夹具的可用性是从测试的角度确定的。只有当夹具定义在测试所在的范围时,测试才能请求该夹具。如果夹具定义在类内部,则只能由该类内部的测试请求。但如果夹具定义在模块的全局范围,则该模块中的每个测试(即使是定义在类内部的测试)都可以请求它。

同样,只有当测试与 autouse 夹具定义在相同范围时,该测试才会被 autouse 夹具影响(参见 autouse 夹具在其作用域内首先执行)。

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

例如,这里有一个测试文件,其中包含一个夹具 (outer),它请求了一个不在其定义范围内的夹具 (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"]

从测试的角度来看,它们可以毫无问题地看到它们所依赖的每个夹具

../_images/test_fixtures_request_different_scope.svg

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

注意

夹具定义的范围对其实例化顺序没有影响:顺序由 此处 描述的逻辑决定。

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

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

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

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

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 文件中的夹具在该整个作用域内可用。

测试允许向上(走出圆圈)搜索夹具,但永远不能向下(走进圆圈)继续搜索。因此 tests/subpackage/test_subpackage.py::test_order 能够找到定义在 tests/subpackage/test_subpackage.py 中的 innermost 夹具,但是定义在 tests/test_top.py 中的夹具对其不可用,因为它必须向下(走进圆圈)一层才能找到它。

测试找到的第一个夹具就是将要使用的夹具,因此如果你需要更改或扩展某个夹具在特定作用域中的功能,夹具可以被覆盖

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

来自第三方插件的夹具

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

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

例如,给定以下文件结构

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 已安装并提供夹具 a_fix,并且 plugin_b 已安装并提供夹具 b_fix,那么测试搜索夹具的方式将如下所示

../_images/fixture_availability_plugins.svg

pytest 会首先在 tests/ 内部的作用域中搜索 a_fixb_fix,然后才会在插件中搜索它们。

夹具实例化顺序

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

  1. 作用域

  2. 依赖关系

  3. 自动使用

夹具或测试的名称、它们的定义位置、它们的定义顺序以及夹具的请求顺序,除了巧合之外,对执行顺序没有影响。虽然 pytest 会尽量确保这些巧合在不同运行之间保持一致,但这不应作为依赖的依据。如果你想控制顺序,最安全的方法是依赖这 3 个因素并确保明确建立依赖关系。

高作用域夹具优先执行

在函数请求夹具时,高作用域(如 session)的夹具会在低作用域(如 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"]

测试将通过,因为作用域更大的夹具会首先执行。

顺序分解如下

../_images/test_fixtures_order_scope.svg

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

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

每个夹具提供的规则(关于每个夹具必须在哪些夹具之后执行)足够全面,可以将其扁平化为如下所示

../_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 夹具在其作用域内首先执行

Autouse 夹具被假定适用于所有可以引用它们的测试,因此它们在该作用域中的其他夹具之前执行。被 autouse 夹具请求的夹具实际上也变成了 autouse 夹具,适用于真实 autouse 夹具所应用的测试。

因此,如果夹具 a 是 autouse 夹具,而夹具 b 不是,但夹具 a 请求夹具 b,那么夹具 b 实际上也会成为一个 autouse 夹具,但仅限于 a 所应用的测试。

在上一个例子中,如果 d 没有请求 c,图就会变得不清晰。但如果 c 是 autouse 夹具,那么 ba 也将实际上成为 autouse 夹具,因为 c 依赖于它们。结果是,它们都将被移到该作用域内非 autouse 夹具的上方。

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

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 夹具。

不过,使用 autouse 要小心,因为 autouse 夹具会自动为每个能够访问它的测试执行,即使它们没有请求它。例如,考虑这个文件

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 夹具请求了一个非 autouse 夹具,并不意味着该非 autouse 夹具就成了所有可应用它的上下文的 autouse 夹具。它只在真实 autouse 夹具(请求该非 autouse 夹具的夹具)可应用的上下文中才有效地成为 autouse 夹具。

例如,看一下这个测试文件

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 夹具,这就是为什么 c2c3 即使没有被请求,也会为这两个测试执行的原因,也是为什么 c2c3test_req 之前执行 c1 的原因。

如果这使得 c2 成为一个 真正 的 autouse 夹具,那么 c2 也将为 TestClassWithoutAutouse 内部的测试执行,因为它们如果愿意的话可以引用 c2。但它不会,因为从 TestClassWithoutAutouse 测试的角度来看,c2 不是一个 autouse 夹具,因为它们看不到 c3