Referenz auf Fixtures

Siehe auch

Über Fixtures

Eingebaute Fixtures

Fixtures werden mit dem @pytest.fixture-Dekorator definiert. Pytest verfügt über mehrere nützliche eingebaute Fixtures

capfd

Erfasst die Ausgabe auf Dateideskriptoren 1 und 2 als Text.

capfdbinary

Erfasst die Ausgabe auf Dateideskriptoren 1 und 2 als Bytes.

caplog

Steuert das Logging und greift auf Log-Einträge zu.

capsys

Erfasst die Ausgabe auf sys.stdout und sys.stderr als Text.

capteesys

Erfasst auf die gleiche Weise wie capsys, leitet aber den Text auch gemäß --capture= weiter.

capsysbinary

Erfasst die Ausgabe auf sys.stdout und sys.stderr als Bytes.

cache

Speichert und ruft Werte über Pytest-Läufe hinweg ab.

doctest_namespace

Stellt ein Dict bereit, das in den Doctest-Namensraum injiziert wird.

monkeypatch

Ändert temporär Klassen, Funktionen, Dictionaries, os.environ und andere Objekte.

pytestconfig

Zugriff auf Konfigurationswerte, Plugin-Manager und Plugin-Hooks.

subtests

Ermöglicht die Deklaration von Subtests innerhalb von Testfunktionen.

record_property

Fügt dem Test zusätzliche Eigenschaften hinzu.

record_testsuite_property

Fügt der Testsuite zusätzliche Eigenschaften hinzu.

recwarn

Zeichnet Warnungen auf, die von Testfunktionen ausgegeben werden.

request

Stellt Informationen über die ausführende Testfunktion bereit.

testdir

Stellt ein temporäres Testverzeichnis zur Verfügung, um das Ausführen und Testen von Pytest-Plugins zu unterstützen.

tmp_path

Stellt ein pathlib.Path-Objekt für ein temporäres Verzeichnis bereit, das für jede Testfunktion eindeutig ist.

tmp_path_factory

Erstellt temporäre Verzeichnisse im Sitzungsbereich und gibt pathlib.Path-Objekte zurück.

tmpdir

Stellt ein py.path.local-Objekt für ein temporäres Verzeichnis bereit, das für jede Testfunktion eindeutig ist; ersetzt durch tmp_path.

tmpdir_factory

Erstellt temporäre Verzeichnisse im Sitzungsbereich und gibt py.path.local-Objekte zurück; ersetzt durch tmp_path_factory.

Fixture-Verfügbarkeit

Die Verfügbarkeit eines Fixtures wird aus der Perspektive des Tests bestimmt. Ein Fixture ist für Tests nur dann verfügbar, wenn sie sich im Bereich befinden, in dem das Fixture definiert ist. Wenn ein Fixture innerhalb einer Klasse definiert ist, kann es nur von Tests innerhalb dieser Klasse angefordert werden. Wenn ein Fixture jedoch im globalen Bereich des Moduls definiert ist, kann es von jedem Test in diesem Modul angefordert werden, auch wenn es innerhalb einer Klasse definiert ist.

Ebenso kann ein Test nur durch ein Autouse-Fixture beeinflusst werden, wenn sich dieser Test im selben Bereich befindet, in dem das Autouse-Fixture definiert ist (siehe Autouse-Fixtures werden zuerst innerhalb ihres Bereichs ausgeführt).

Ein Fixture kann auch jedes andere Fixture anfordern, unabhängig davon, wo es definiert ist, solange der Test, der es anfordert, alle beteiligten Fixtures sehen kann.

Hier ist zum Beispiel eine Testdatei mit einem Fixture (outer), das ein Fixture (inner) aus einem Bereich anfordert, in dem es nicht definiert war

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"]

Aus der Perspektive der Tests können sie problemlos jedes der Fixtures sehen, von denen sie abhängen

../_images/test_fixtures_request_different_scope.svg

Wenn sie ausgeführt werden, hat outer kein Problem, inner zu finden, da Pytest aus der Perspektive der Tests gesucht hat.

Hinweis

Der Bereich, in dem ein Fixture definiert ist, hat keinen Einfluss auf die Reihenfolge, in der es instanziiert wird: Die Reihenfolge wird durch die Logik bestimmt, die hier beschrieben ist.

conftest.py: Teilen von Fixtures über mehrere Dateien hinweg

Die Datei conftest.py dient zur Bereitstellung von Fixtures für ein gesamtes Verzeichnis. In einer conftest.py definierte Fixtures können von jedem Test in diesem Paket verwendet werden, ohne importiert werden zu müssen (Pytest erkennt sie automatisch).

Sie können mehrere verschachtelte Verzeichnisse/Pakete mit Ihren Tests haben, und jedes Verzeichnis kann seine eigene conftest.py mit seinen eigenen Fixtures haben, die zu den von den conftest.py-Dateien in übergeordneten Verzeichnissen bereitgestellten Fixtures hinzugefügt werden.

Beispielweise bei einer Testdateistruktur wie dieser

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"]

Die Grenzen der Bereiche können wie folgt visualisiert werden

../_images/fixture_availability.svg

Die Verzeichnisse werden zu einer Art Bereich, in dem in einer conftest.py-Datei definierte Fixtures für diesen gesamten Bereich verfügbar werden.

Tests dürfen nach oben suchen (einen Kreis verlassen), aber niemals nach unten gehen (einen Kreis betreten), um ihre Suche fortzusetzen. tests/subpackage/test_subpackage.py::test_order könnte also das in tests/subpackage/test_subpackage.py definierte Fixture innermost finden, aber das in tests/test_top.py definierte wäre für es nicht verfügbar, da es eine Ebene nach unten steigen müsste (einen Kreis betreten), um es zu finden.

Das erste Fixture, das der Test findet, ist dasjenige, das verwendet wird, daher können Fixtures überschrieben werden, wenn Sie ändern oder erweitern müssen, was eines für einen bestimmten Bereich tut.

Sie können die conftest.py-Datei auch verwenden, um lokale pro-Verzeichnis-Plugins zu implementieren.

Fixtures von Drittanbieter-Plugins

Fixtures müssen jedoch nicht in dieser Struktur definiert sein, um für Tests verfügbar zu sein. Sie können auch von installierten Drittanbieter-Plugins bereitgestellt werden, und so funktionieren viele Pytest-Plugins. Solange diese Plugins installiert sind, können die von ihnen bereitgestellten Fixtures überall in Ihrer Testsuite angefordert werden.

Da sie von außerhalb der Struktur Ihrer Testsuite bereitgestellt werden, bieten Drittanbieter-Plugins keinen wirklichen Bereich wie conftest.py-Dateien und die Verzeichnisse Ihrer Testsuite. Daher durchsucht Pytest die Fixtures, indem es sich wie zuvor erklärt durch die Bereiche nach oben arbeitet und erst *zuletzt* die in Plugins definierten Fixtures erreicht.

Zum Beispiel bei der folgenden Dateistruktur

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"]

Wenn plugin_a installiert ist und das Fixture a_fix bereitstellt und plugin_b installiert ist und das Fixture b_fix bereitstellt, dann sieht die Suche des Tests nach Fixtures wie folgt aus

../_images/fixture_availability_plugins.svg

Pytest durchsucht die Plugins nur nach a_fix und b_fix, nachdem es zuerst in den Bereichen innerhalb von tests/ danach gesucht hat.

Fixture-Instanziierungsreihenfolge

Wenn Pytest einen Test ausführen möchte, muss es, sobald es weiß, welche Fixtures ausgeführt werden, die Reihenfolge ermitteln, in der sie ausgeführt werden. Dazu werden 3 Faktoren berücksichtigt

  1. scope

  2. dependencies

  3. autouse

Namen von Fixtures oder Tests, wo sie definiert sind, die Reihenfolge, in der sie definiert sind, und die Reihenfolge, in der Fixtures angefordert werden, haben keinen Einfluss auf die Ausführungsreihenfolge, abgesehen von Zufällen. Obwohl Pytest versuchen wird, sicherzustellen, dass Zufälle wie diese von Lauf zu Lauf konsistent bleiben, sollte man sich nicht darauf verlassen. Wenn Sie die Reihenfolge steuern möchten, ist es am sichersten, sich auf diese 3 Dinge zu verlassen und sicherzustellen, dass Abhängigkeiten klar etabliert sind.

Fixtures mit höherem Scope werden zuerst ausgeführt

Bei einer funktionsbasierten Anforderung von Fixtures werden diejenigen mit höherem Scope (z. B. session) vor denen mit niedrigerem Scope (z. B. function oder class) ausgeführt.

Hier ist ein Beispiel

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"]

Der Test wird erfolgreich sein, da die größeren scoped Fixtures zuerst ausgeführt werden.

Die Reihenfolge gliedert sich wie folgt

../_images/test_fixtures_order_scope.svg

Fixtures derselben Reihenfolge werden basierend auf Abhängigkeiten ausgeführt

Wenn ein Fixture ein anderes Fixture anfordert, wird das andere Fixture zuerst ausgeführt. Wenn also Fixture a Fixture b anfordert, wird Fixture b zuerst ausgeführt, da a von b abhängt und ohne es nicht funktionieren kann. Selbst wenn a das Ergebnis von b nicht benötigt, kann es b anfordern, wenn es sicherstellen muss, dass es nach b ausgeführt wird.

Zum Beispiel

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"]

Wenn wir abbilden, was wovon abhängt, erhalten wir etwas, das so aussieht

../_images/test_fixtures_order_dependencies.svg

Die von jedem Fixture bereitgestellten Regeln (bezüglich welcher Fixture(s) es nachkommen muss) sind so umfassend, dass sie wie folgt abgeflacht werden können

../_images/test_fixtures_order_dependencies_flat.svg

Es müssen genügend Informationen über diese Anfragen bereitgestellt werden, damit Pytest eine klare, lineare Abhängigkeitskette und damit eine Ausführungsreihenfolge für einen bestimmten Test ermitteln kann. Wenn es Unklarheiten gibt und die Ausführungsreihenfolge mehrdeutig interpretiert werden kann, sollten Sie davon ausgehen, dass Pytest zu jedem Zeitpunkt eine dieser Interpretationen wählen könnte.

Wenn zum Beispiel d nicht c anfordern würde, d.h. der Graph würde wie folgt aussehen

../_images/test_fixtures_order_dependencies_unclear.svg

Da nichts c angefordert hat außer g, und g auch f anfordert, ist es nun unklar, ob c vor/nach f, e oder d gehen sollte. Die einzigen Regeln, die für c festgelegt wurden, sind, dass es nach b und vor g ausgeführt werden muss.

Pytest weiß in diesem Fall nicht, wo c hingehen soll, daher sollte davon ausgegangen werden, dass es überall zwischen g und b gehen könnte.

Das ist nicht unbedingt schlecht, aber etwas, das man im Hinterkopf behalten sollte. Wenn die Reihenfolge, in der sie ausgeführt werden, das Verhalten eines Tests beeinflusst, auf das gezielt wird, oder anderweitig das Ergebnis eines Tests beeinflussen könnte, dann sollte die Reihenfolge explizit so definiert werden, dass Pytest diese Reihenfolge linearisieren/„abflachen“ kann.

Autouse-Fixtures werden zuerst innerhalb ihres Bereichs ausgeführt

Autouse-Fixtures werden angenommen, dass sie für jeden Test gelten, der auf sie verweisen kann, daher werden sie vor anderen Fixtures in diesem Bereich ausgeführt. Fixtures, die von Autouse-Fixtures angefordert werden, werden für die Tests, für die das eigentliche Autouse-Fixture gilt, effektiv selbst zu Autouse-Fixtures.

Wenn also Fixture a autouse ist und Fixture b nicht, aber Fixture a Fixture b anfordert, dann wird Fixture b effektiv auch ein Autouse-Fixture sein, aber nur für die Tests, für die a gilt.

Im letzten Beispiel wurde der Graph unklar, wenn d nicht c anforderte. Aber wenn c autouse wäre, dann wären b und a ebenfalls effektiv autouse, da c von ihnen abhängt. Infolgedessen würden sie alle innerhalb dieses Bereichs über nicht-autouse Fixtures verschoben.

Wenn also die Testdatei wie folgt aussehen würde

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"]

würde der Graph so aussehen

../_images/test_fixtures_order_autouse.svg

Da c nun über d im Graphen platziert werden kann, kann Pytest den Graphen wieder wie folgt linearisieren

../_images/test_fixtures_order_autouse_flat.svg

In diesem Beispiel macht c auch b und a zu effektiv autouse Fixtures.

Seien Sie jedoch vorsichtig mit Autouse, da ein Autouse-Fixture automatisch für jeden Test ausgeführt wird, der es erreichen kann, auch wenn er es nicht anfordert. Betrachten Sie zum Beispiel diese Datei

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"]

Obwohl nichts in TestClassWithoutC1Request c1 anfordert, wird es trotzdem für die Tests darin ausgeführt

../_images/test_fixtures_order_autouse_multiple_scopes.svg

Aber nur weil ein Autouse-Fixture ein Nicht-Autouse-Fixture angefordert hat, heißt das nicht, dass das Nicht-Autouse-Fixture für alle Kontexte, für die es gilt, zu einem Autouse-Fixture wird. Es wird nur effektiv zu einem Autouse-Fixture für die Kontexte, für die das eigentliche Autouse-Fixture (dasjenige, das das Nicht-Autouse-Fixture angefordert hat) gilt.

Betrachten Sie zum Beispiel diese Testdatei

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 == []

Es würde sich wie folgt aufschlüsseln

../_images/test_fixtures_order_autouse_temp_effects.svg

Für test_req und test_no_req innerhalb von TestClassWithAutouse macht c3 c2 effektiv zu einem Autouse-Fixture, weshalb c2 und c3 für beide Tests ausgeführt werden, obwohl sie nicht angefordert wurden, und warum c2 und c3 vor c1 für test_req ausgeführt werden.

Wenn dies c2 zu einem *tatsächlichen* Autouse-Fixture machen würde, dann würde c2 auch für die Tests innerhalb von TestClassWithoutAutouse ausgeführt werden, da sie auf c2 verweisen könnten, wenn sie wollten. Aber das tut es nicht, denn aus der Perspektive der Tests von TestClassWithoutAutouse ist c2 kein Autouse-Fixture, da sie c3 nicht sehen können.