Wie man Fixtures und Testfunktionen parametrisiert¶
pytest ermöglicht Testparametrisierung auf mehreren Ebenen
pytest.fixture()erlaubt es, Fixture-Funktionen zu parametrisieren.
@pytest.mark.parametrize erlaubt es, mehrere Sätze von Argumenten und Fixtures auf Testfunktionsebene oder Klassenebene zu definieren.
pytest_generate_tests erlaubt es, benutzerdefinierte Parametrisierungsschemata oder Erweiterungen zu definieren.
Hinweis
Siehe Wie man Subtests verwendet für eine Alternative zur Parametrisierung.
@pytest.mark.parametrize: Testfunktionen parametrisieren¶
Der eingebaute Dekorator pytest.mark.parametrize ermöglicht die Parametrisierung von Argumenten für eine Testfunktion. Hier ist ein typisches Beispiel einer Testfunktion, die überprüft, ob eine bestimmte Eingabe zu einer erwarteten Ausgabe führt
# content of test_expectation.py
import pytest
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
assert eval(test_input) == expected
Hier definiert der @parametrize Dekorator drei verschiedene (test_input,expected) Tupel, so dass die test_eval Funktion dreimal mit diesen nacheinander ausgeführt wird
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-9.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 3 items
test_expectation.py ..F [100%]
================================= FAILURES =================================
____________________________ test_eval[6*9-42] _____________________________
test_input = '6*9', expected = 42
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
> assert eval(test_input) == expected
E AssertionError: assert 54 == 42
E + where 54 = eval('6*9')
test_expectation.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_expectation.py::test_eval[6*9-42] - AssertionError: assert 54...
======================= 1 failed, 2 passed in 0.12s ========================
Hinweis
Parameterwerte werden unverändert an Tests übergeben (keine Kopie).
Wenn Sie beispielsweise eine Liste oder ein Dictionary als Parameterwert übergeben und der Testfallcode diese mutiert, werden die Änderungen in nachfolgenden Testfallaufrufen reflektiert.
Hinweis
pytest maskiert standardmäßig alle Nicht-ASCII-Zeichen, die in Unicode-Strings für die Parametrisierung verwendet werden, da dies mehrere Nachteile hat. Wenn Sie jedoch Unicode-Strings in der Parametrisierung verwenden und diese unverändert im Terminal sehen möchten (nicht maskiert), verwenden Sie diese Option in Ihrer Konfigurationsdatei
[pytest]
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = true
[pytest]
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = true
Beachten Sie jedoch, dass dies unerwünschte Nebenwirkungen und sogar Fehler verursachen kann, abhängig vom verwendeten Betriebssystem und den aktuell installierten Plugins. Verwenden Sie es daher auf eigene Gefahr.
Wie in diesem Beispiel vorgesehen, schlägt nur ein Paar von Ein- und Ausgabewerten im einfachen Testfall fehl. Und wie üblich bei Argumenten von Testfunktionen können Sie die Werte für input und output im Traceback sehen.
Beachten Sie, dass Sie auch den parametrize-Marker auf eine Klasse oder ein Modul anwenden können (siehe Wie man Testfunktionen mit Attributen markiert), was mehrere Funktionen mit den Argumentensätzen aufrufen würde, zum Beispiel
import pytest
@pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
class TestClass:
def test_simple_case(self, n, expected):
assert n + 1 == expected
def test_weird_simple_case(self, n, expected):
assert (n * 1) + 1 == expected
Um alle Tests in einem Modul zu parametrisieren, können Sie der globalen Variablen pytestmark Werte zuweisen
import pytest
pytestmark = pytest.mark.parametrize("n,expected", [(1, 2), (3, 4)])
class TestClass:
def test_simple_case(self, n, expected):
assert n + 1 == expected
def test_weird_simple_case(self, n, expected):
assert (n * 1) + 1 == expected
Es ist auch möglich, einzelne Testinstanzen innerhalb von parametrize zu markieren, zum Beispiel mit dem eingebauten mark.xfail
# content of test_expectation.py
import pytest
@pytest.mark.parametrize(
"test_input,expected",
[("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks=pytest.mark.xfail)],
)
def test_eval(test_input, expected):
assert eval(test_input) == expected
Lassen Sie uns das ausführen
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-9.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 3 items
test_expectation.py ..x [100%]
======================= 2 passed, 1 xfailed in 0.12s =======================
Der eine Parametersatz, der zuvor einen Fehler verursacht hat, wird nun als "xfailed" (erwartet zu fehlschlagen) Test angezeigt.
Wenn die an parametrize übergebenen Werte eine leere Liste ergeben - zum Beispiel, wenn sie dynamisch von einer Funktion generiert werden - wird das Verhalten von pytest durch die Option empty_parameter_set_mark bestimmt.
Um alle Kombinationen mehrerer parametrisierter Argumente zu erhalten, können Sie parametrize Dekoratoren stapeln
import pytest
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
pass
Dies führt den Test mit den Argumentensätzen x=0/y=2, x=1/y=2, x=0/y=3 und x=1/y=3 aus, wobei die Parameter in der Reihenfolge der Dekoratoren erschöpft werden.
Einfaches Beispiel für pytest_generate_tests¶
Manchmal möchten Sie vielleicht Ihr eigenes Parametrisierungsschema implementieren oder einige Dynamiken zur Bestimmung der Parameter oder des Geltungsbereichs einer Fixture implementieren. Hierfür können Sie den pytest_generate_tests Hook verwenden, der beim Sammeln einer Testfunktion aufgerufen wird. Über das übergebene metafunc Objekt können Sie den anfragenden Testkontext inspizieren und, am wichtigsten, metafunc.parametrize() aufrufen, um Parametrisierung zu bewirken.
Nehmen wir zum Beispiel an, wir möchten einen Test ausführen, der Zeichenketteneingaben entgegennimmt, die wir über eine neue pytest Kommandozeilenoption setzen möchten. Schreiben wir zuerst einen einfachen Test, der ein stringinput Fixture-Funktionsargument akzeptiert
# content of test_strings.py
def test_valid_string(stringinput):
assert stringinput.isalpha()
Nun fügen wir eine conftest.py Datei hinzu, die die Ergänzung einer Kommandozeilenoption und die Parametrisierung unserer Testfunktion enthält
# content of conftest.py
def pytest_addoption(parser):
parser.addoption(
"--stringinput",
action="append",
default=[],
help="list of stringinputs to pass to test functions",
)
def pytest_generate_tests(metafunc):
if "stringinput" in metafunc.fixturenames:
metafunc.parametrize("stringinput", metafunc.config.getoption("stringinput"))
Hinweis
Der pytest_generate_tests Hook kann auch direkt in einem Testmodul oder innerhalb einer Testklasse implementiert werden; im Gegensatz zu anderen Hooks wird pytest ihn auch dort entdecken. Andere Hooks müssen in einer conftest.py oder einem Plugin leben. Siehe Hook-Funktionen schreiben.
Wenn wir nun zwei stringinput-Werte übergeben, wird unser Test zweimal ausgeführt
$ pytest -q --stringinput="hello" --stringinput="world" test_strings.py
.. [100%]
2 passed in 0.12s
Lassen Sie uns auch mit einem stringinput ausführen, der zu einem fehlschlagenden Test führt
$ pytest -q --stringinput="!" test_strings.py
F [100%]
================================= FAILURES =================================
___________________________ test_valid_string[!] ___________________________
stringinput = '!'
def test_valid_string(stringinput):
> assert stringinput.isalpha()
E AssertionError: assert False
E + where False = <built-in method isalpha of str object at 0xdeadbeef0001>()
E + where <built-in method isalpha of str object at 0xdeadbeef0001> = '!'.isalpha
test_strings.py:4: AssertionError
========================= short test summary info ==========================
FAILED test_strings.py::test_valid_string[!] - AssertionError: assert False
1 failed in 0.12s
Wie erwartet schlägt unsere Testfunktion fehl.
Wenn Sie keinen stringinput angeben, wird er übersprungen, da metafunc.parametrize() mit einer leeren Parameterliste aufgerufen wird
$ pytest -q -rs test_strings.py
s [100%]
========================= short test summary info ==========================
SKIPPED [1] test_strings.py: got empty parameter set for (stringinput)
1 skipped in 0.12s
Beachten Sie, dass bei mehrmaligem Aufruf von metafunc.parametrize mit unterschiedlichen Parametersätzen alle Parameternamen über diese Sätze hinweg nicht dupliziert werden dürfen, da sonst ein Fehler ausgelöst wird.
Weitere Beispiele¶
Für weitere Beispiele sollten Sie sich weitere Parametrisierungsbeispiele ansehen.