Fixtures 參考

內建 fixtures

Fixtures 是使用 @pytest.fixture 裝飾器定義的。Pytest 有幾個有用的內建 fixtures

capfd

以文字形式捕獲到檔案描述符 12 的輸出。

capfdbinary

以位元組形式捕獲到檔案描述符 12 的輸出。

caplog

控制日誌記錄並存取日誌條目。

capsys

以文字形式捕獲到 sys.stdoutsys.stderr 的輸出。

capsysbinary

以位元組形式捕獲到 sys.stdoutsys.stderr 的輸出。

cache

跨 pytest 運行儲存和檢索值。

doctest_namespace

提供一個注入到 doctests 命名空間中的 dict。

monkeypatch

臨時修改類別、函數、字典、os.environ 和其他物件。

pytestconfig

存取配置值、插件管理器和插件 hooks。

record_property

向測試新增額外屬性。

record_testsuite_property

向測試套件新增額外屬性。

recwarn

記錄測試函數發出的警告。

request

提供有關正在執行的測試函數的資訊。

testdir

提供一個臨時測試目錄,以協助運行和測試 pytest 插件。

tmp_path

提供一個 pathlib.Path 物件,指向每個測試函數唯一的臨時目錄。

tmp_path_factory

建立 session 作用域的臨時目錄並傳回 pathlib.Path 物件。

tmpdir

提供一個 py.path.local 物件,指向每個測試函數唯一的臨時目錄;已被 tmp_path 取代。

tmpdir_factory

建立 session 作用域的臨時目錄並傳回 py.path.local 物件;已被 tmp_path_factory 取代。

Fixture 可用性

Fixture 的可用性是從測試的角度決定的。只有當 fixture 位於定義它的作用域中時,測試才能請求 fixture。如果 fixture 在類別內部定義,則只能由該類別內部的測試請求。但是,如果 fixture 在模組的全局作用域內定義,則該模組中的每個測試,即使它在類別內部定義,也可以請求它。

同樣地,只有當測試與 autouse fixture 定義在相同的作用域中時,測試才會受到 autouse fixture 的影響(請參閱 Autouse fixtures 在其作用域內首先執行)。

Fixture 也可以請求任何其他 fixture,無論它在哪裡定義,只要請求它們的測試可以看到所有相關的 fixtures。

例如,這是一個測試檔案,其中包含一個 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:跨多個檔案共享 fixtures

conftest.py 檔案用作為整個目錄提供 fixtures 的一種方式。在 conftest.py 中定義的 Fixtures 可以被該套件中的任何測試使用,而無需導入它們(pytest 將自動發現它們)。

您可以擁有包含測試的多個巢狀目錄/套件,並且每個目錄都可以有自己的 conftest.py 及其自己的 fixtures,添加到父目錄中 conftest.py 檔案提供的 fixtures。

例如,給定如下的測試檔案結構

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 檔案中定義的 fixtures 對於整個作用域都可用。

允許測試向上搜索(步出圓圈)fixtures,但永遠不能向下(步入圓圈)繼續搜索。因此,tests/subpackage/test_subpackage.py::test_order 將能夠找到在 tests/subpackage/test_subpackage.py 中定義的 innermost fixture,但在 tests/test_top.py 中定義的 fixture 對它不可用,因為它必須向下移動一個層級(步入圓圈)才能找到它。

測試找到的第一個 fixture 是將被使用的 fixture,因此如果需要更改或擴展特定作用域的 fixture 的功能,可以覆蓋 fixtures

您還可以使用 conftest.py 檔案來實現 本地每個目錄插件

來自第三方插件的 Fixtures

Fixture 不必在此結構中定義才能用於測試。它們也可以由已安裝的第三方插件提供,這也是許多 pytest 插件的運作方式。只要安裝了這些插件,它們提供的 fixtures 就可以從測試套件中的任何位置請求。

由於它們是從測試套件結構外部提供的,因此第三方插件實際上不提供像 conftest.py 檔案和測試套件中的目錄那樣的作用域。因此,pytest 將按照先前解釋的方式,步出作用域搜索 fixtures,僅最後到達在插件中定義的 fixtures。

例如,給定以下檔案結構

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,那麼這就是測試搜索 fixtures 的方式

../_images/fixture_availability_plugins.svg

pytest 將僅在插件中搜索 a_fixb_fix,然後才在 tests/ 內的作用域中首先搜索它們。

Fixture 實例化順序

當 pytest 想要執行測試時,一旦它知道將要執行的 fixtures,它就必須弄清楚它們將以什麼順序執行。為此,它考慮了 3 個因素

  1. 作用域

  2. 依賴關係

  3. autouse

Fixture 或測試的名稱、它們的定義位置、它們的定義順序以及請求 fixtures 的順序與執行順序無關,除了巧合。雖然 pytest 會盡力確保這些巧合在每次運行中保持一致,但這不應該依賴。如果您想控制順序,最安全的方法是依賴這 3 件事,並確保依賴關係已明確建立。

較高作用域的 fixtures 首先執行

在對 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

相同順序的 fixtures 根據依賴關係執行

當 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 fixtures 在其作用域內首先執行

Autouse fixtures 假定適用於每個可以引用它們的測試,因此它們在該作用域中的其他 fixtures 之前執行。由 autouse fixtures 請求的 fixtures 實際上也變成了真實 autouse fixture 適用的測試的 autouse fixtures。

因此,如果 fixture a 是 autouse,而 fixture b 不是,但 fixture a 請求 fixture b,則 fixture b 實際上也將成為 autouse fixture,但僅適用於 a 適用的測試。

在最後一個範例中,如果 d 沒有請求 c,則圖表變得不清楚。但是,如果 c 是 autouse,則 ba 實際上也將是 autouse,因為 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,因為 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 的 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