夹具参考

另请参阅

关于夹具

另请参阅

如何使用夹具

内置夹具

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

capfd

作为文本捕获,输出到文件描述符 12

capfdbinary

作为字节捕获,输出到文件描述符 12

caplog

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

capsys

作为文本捕获,输出到 sys.stdoutsys.stderr

capsysbinary

作为字节捕获,输出到 sys.stdoutsys.stderr

cache

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

doctest_namespace

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

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

夹具可用性

固定装置的可用性由测试的角度决定。只有在固定装置定义的范围内,测试才能请求固定装置。如果固定装置在类内定义,则只能由该类内的测试请求。但如果固定装置在模块的全局范围内定义,那么该模块中的每个测试,即使在类内定义,都可以请求它。

类似地,测试也只有在与自动使用固定装置定义的范围相同的情况下,才会受到自动使用固定装置的影响(请参见 自动使用固定装置在其范围内首先执行)。

固定装置还可以请求任何其他固定装置,无论其定义在哪里,只要请求它们的测试可以看到所有涉及的固定装置即可。

例如,这里有一个带有固定装置(outer)的测试文件,它从未定义的范围内请求固定装置(inner

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 将仅在插件中搜索 a_fixb_fix,此前已在 tests/ 内的范围内搜索过它们。

夹具实例化顺序

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

  1. 范围

  2. 依赖项

  3. 自动使用

夹具或测试的名称、定义位置、定义顺序以及请求夹具的顺序除了巧合之外,与执行顺序无关。虽然 pytest 会尝试确保这些巧合在每次运行中保持一致,但这不是应该依赖的东西。如果您想控制顺序,最安全的方法是依赖这 3 件事,并确保明确建立依赖项。

范围较高的夹具先执行

在夹具的功能请求中,范围较高的夹具(例如 session)在范围较低的夹具(例如 functionclass)之前执行。

以下是一个示例

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

例如

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 夹具上方移动。

因此,如果测试文件如下所示

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 夹具将自动为可以访问它的每个测试执行,即使它们没有请求它。例如,考虑此文件

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

但仅仅因为一个自动使用装置请求了一个非自动使用装置,并不意味着非自动使用装置将成为它可以应用的所有上下文的自动使用装置。它仅有效地成为真实自动使用装置(请求非自动使用装置的那个)可以应用的上下文的自动使用装置。

例如,看看这个测试文件

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

对于 test_reqtest_no_reqTestClassWithAutouse 中,c3 有效地使 c2 成为一个自动使用装置,这就是为什么 c2c3 针对两个测试执行,尽管没有被请求,以及为什么 c2c3test_req 之前针对 c1 执行。

如果这使 c2 成为一个实际的自动使用装置,那么 c2 也会针对 TestClassWithoutAutouse 中的测试执行,因为如果它们想要,它们可以引用 c2。但它不会,因为从 TestClassWithoutAutouse 测试的角度来看,c2 不是一个自动使用装置,因为它们看不到 c3