Referenz auf Fixtures¶
Siehe auch
Siehe auch
Eingebaute Fixtures¶
Fixtures werden mit dem @pytest.fixture-Dekorator definiert. Pytest verfügt über mehrere nützliche eingebaute Fixtures
capfdErfasst die Ausgabe auf Dateideskriptoren
1und2als Text.capfdbinaryErfasst die Ausgabe auf Dateideskriptoren
1und2als Bytes.caplogSteuert das Logging und greift auf Log-Einträge zu.
capsysErfasst die Ausgabe auf
sys.stdoutundsys.stderrals Text.capteesysErfasst auf die gleiche Weise wie
capsys, leitet aber den Text auch gemäß--capture=weiter.capsysbinaryErfasst die Ausgabe auf
sys.stdoutundsys.stderrals Bytes.cacheSpeichert und ruft Werte über Pytest-Läufe hinweg ab.
doctest_namespaceStellt ein Dict bereit, das in den Doctest-Namensraum injiziert wird.
monkeypatchÄndert temporär Klassen, Funktionen, Dictionaries,
os.environund andere Objekte.pytestconfigZugriff auf Konfigurationswerte, Plugin-Manager und Plugin-Hooks.
subtestsErmöglicht die Deklaration von Subtests innerhalb von Testfunktionen.
record_propertyFügt dem Test zusätzliche Eigenschaften hinzu.
record_testsuite_propertyFügt der Testsuite zusätzliche Eigenschaften hinzu.
recwarnZeichnet Warnungen auf, die von Testfunktionen ausgegeben werden.
requestStellt Informationen über die ausführende Testfunktion bereit.
testdirStellt ein temporäres Testverzeichnis zur Verfügung, um das Ausführen und Testen von Pytest-Plugins zu unterstützen.
tmp_pathStellt ein
pathlib.Path-Objekt für ein temporäres Verzeichnis bereit, das für jede Testfunktion eindeutig ist.tmp_path_factoryErstellt temporäre Verzeichnisse im Sitzungsbereich und gibt
pathlib.Path-Objekte zurück.tmpdirStellt ein py.path.local-Objekt für ein temporäres Verzeichnis bereit, das für jede Testfunktion eindeutig ist; ersetzt durch
tmp_path.tmpdir_factoryErstellt temporäre Verzeichnisse im Sitzungsbereich und gibt
py.path.local-Objekte zurück; ersetzt durchtmp_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
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
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
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
scope
dependencies
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
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
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
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
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
Da c nun über d im Graphen platziert werden kann, kann Pytest den Graphen wieder wie folgt linearisieren
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
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
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.