固定裝置參考

另請參閱

關於固定裝置

內建固定裝置

固定裝置使用 @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

存取組態值、pluginmanager 和外掛程式掛鉤。

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 會在先在 tests/ 內的範圍中搜尋 a_fixb_fix 之後,才會在外掛程式中搜尋它們。

固定裝置實例化順序

當 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 是自動使用,則 ba 也會有效地變成自動使用,因為 c 取決於它們。結果,它們都會在該範圍內移到非自動使用固定裝置之上。

因此,如果測試檔案看起來像這樣

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 也有效地變成自動使用固定裝置。

不過,請小心自動使用,因為自動使用固定裝置會自動執行每個可以觸及它的測試,即使它們沒有要求它。例如,考慮這個檔案

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

對於 TestClassWithAutouse 中的 test_reqtest_no_reqc3 有效地使 c2 成為自動使用固定裝置,這就是為什麼 c2c3 會在兩個測試中執行,儘管沒有要求,而且為什麼 c2c3 會在 test_reqc1 之前執行。

如果這讓 c2 成為一個實際的自動使用固定裝置,那麼 c2 也會對 TestClassWithoutAutouse 內部的測試執行,因為如果他們想要,他們可以參考 c2。但它不會執行,因為從 TestClassWithoutAutouse 測試的角度來看,c2 不是一個自動使用固定裝置,因為他們看不到 c3