Einführung in das Testen von Python-Code mit PyTest
PyTest ist eine beliebte Testbibliothek für Python. Es handelt sich um ein einfaches, benutzerfreundliches Test-Framework, mit dem Sie Tests für Ihren Python-Code schreiben und ausführen können. Hier finden Sie eine kurze Einführung in die Verwendung von PyTest

Ein Test ist Code, der Code ausführt. Wenn Sie mit der Entwicklung einer neuen Funktion für Ihr Python-Projekt beginnen, können Sie die Anforderungen als Code formalisieren. Damit dokumentieren Sie nicht nur die Art und Weise, wie der Code Ihrer Implementierung verwendet werden soll, sondern Sie können auch alle Tests automatisch ausführen lassen, um sicherzustellen, dass Ihr Code immer den Anforderungen entspricht. Ein solches Tool, das Ihnen dabei hilft, ist pytest
und es ist wahrscheinlich das beliebteste Testwerkzeug im Python-Universum.
Es dreht sich alles um assert
Nehmen wir an, Sie haben eine Funktion geschrieben, die eine E-Mail-Adresse validiert. Beachten Sie, dass wir es hier einfach halten und keine regulären Ausdrücke oder DNS-Tests für die Überprüfung von E-Mail-Adressen verwenden. Stattdessen stellen wir nur sicher, dass die zu prüfende Zeichenkette genau ein @
-Zeichen und nur lateinische Zeichen, Zahlen und die Zeichen .
, -
und _
enthält.
|
|
Jetzt haben wir einige Assertionen für unseren Code. Wir behaupten zum Beispiel, dass diese E-Mail-Adressen gültig sind:
Andererseits würden wir erwarten, dass unsere Funktion für E-Mail-Adressen wie diese den Wert False zurückgibt:
not [email protected]
- enthält platzjohn.doe
fehlendes @ zeichenjohn,[email protected]
enthält komma
Wir können überprüfen, ob sich unsere Funktion tatsächlich so verhält, wie wir es erwarten:
|
|
Diese E-Mail-Adressbeispiele, die wir uns ausdenken, werden Testfälle genannt. Für jeden Testfall erwarten wir ein bestimmtes Ergebnis. Ein Tool wie pytest
kann dabei helfen, das Testen dieser Behauptungen zu automatisieren. Das Aufschreiben dieser Behauptungen kann Ihnen helfen,:
- zu dokumentieren, wie Ihr Code verwendet werden soll
- sicherzustellen, dass zukünftige Änderungen nicht andere Teile Ihrer Software beschädigen
- über mögliche Randfälle Ihrer Funktionalitäten nachzudenken
Um dies zu erreichen, erstellen wir einfach eine neue Datei für alle unsere Tests und fügen dort ein paar Funktionen ein.
|
|
Laufende Tests
Einfaches Beispiel
Wir haben also zwei Dateien in unserem Projektverzeichnis: validator.py
und test_validator.py
.
Wir können nun einfach pytest
von der Kommandozeile aus starten. Die Ausgabe sollte in etwa so aussehen:
|
|
Hier informiert uns pytest
, dass es drei Testfunktionen in test_validator.py
gefunden hat und dass alle diese Funktionen bestanden wurden (wie durch die drei Punkte ...
angezeigt).
Die Anzeige 100%
gibt uns ein gutes Gefühl, da wir sicher sind, dass unser Validator wie erwartet funktioniert. Wie in der Einleitung beschrieben, ist die Validierungsfunktion jedoch alles andere als perfekt. Und das gilt auch für unsere Testfälle. Auch ohne DNS-Tests würden wir eine E-Mail-Adresse wie [email protected]
als gültig markieren, während eine Adresse wie [email protected]
als ungültig markiert würde.
Fügen wir diese Testfälle nun zu unserer test_validator.py
hinzu
|
|
Wenn wir pytest
erneut ausführen, sehen wir fehlgeschlagene Tests:
|
|
Beachten Sie, dass wir zusätzlich zu unseren drei ...
Punkten zwei FF
erhalten haben, um anzuzeigen, dass zwei Testfunktionen fehlgeschlagen sind.
Außerdem erhalten wir einen neuen Abschnitt FAILURES
in unserer Ausgabe, der detailliert erklärt, an welchem Punkt unser Test fehlgeschlagen ist. Das ist bei der Fehlersuche sehr hilfreich.
Entwerfen von Tests
Unser kleines Validierungsbeispiel zeigt, wie wichtig die Entwicklung von Tests ist.
Wir haben zuerst unsere Validierungsfunktion geschrieben und uns dann einige Testfälle dafür ausgedacht. Bald stellten wir fest, dass diese Testfälle keineswegs umfassend sind. Stattdessen haben wir einige wesentliche Aspekte der Validierung einer E-Mail-Adresse ausgelassen.
Vielleicht haben Sie schon von Test Driven Development (TDD) gehört, das für das genaue Gegenteil eintritt: Sie schreiben zuerst Ihre Testfälle, um die Anforderungen zu erfüllen, und beginnen erst dann mit der Implementierung einer Funktion, wenn Sie das Gefühl haben, alle Testfälle abgedeckt zu haben. Diese Denkweise war schon immer eine gute Idee, hat aber im Laufe der Zeit noch mehr an Bedeutung gewonnen, da die Komplexität von Softwareprojekten zugenommen hat.
Ich werde demnächst einen weiteren Blogbeitrag über TDD schreiben, in dem ich dieses Thema ausführlich behandeln werde.
Konfiguration
Normalerweise ist die Einrichtung eines Projekts viel komplizierter als nur eine einzelne Datei mit einer Validierungsfunktion darin.
Vielleicht haben Sie eine Python-Paketstruktur für Ihr Projekt, oder Ihr Code ist von externen Abhängigkeiten wie einer Datenbank abhängig.
Fixtures
Vielleicht haben Sie den Begriff Fixture schon in anderen Zusammenhängen verwendet. Beim Django-Web-Framework zum Beispiel beziehen sich Fixtures auf eine Sammlung von Ausgangsdaten, die in die Datenbank geladen werden. Im Kontext von pytest
beziehen sich Fixtures jedoch nur auf Funktionen, die von pytest
vor und/oder nach den eigentlichen Testfunktionen ausgeführt werden.
Einrichten und Abreißen
Wir können solche Funktionen mit dem Dekorator pytest.fixture()
erstellen. Wir tun dies zunächst innerhalb der Datei test_validator.py
.
|
|
Beachten Sie, dass das Einrichten der Datenbank und das Abbauen der Datenbank in derselben Fixture erfolgen. Das Schlüsselwort yield
bezeichnet den Teil, in dem pytest
die eigentlichen Tests ausführt.
Damit die Fixture tatsächlich von einem Ihrer Tests verwendet wird, fügen Sie einfach den Namen der Fixture als Argument hinzu, etwa so (noch in test_validator.py
):
|
|
Daten von Fixtures erhalten
Anstatt yield
zu verwenden, kann eine Fixture-Funktion auch beliebige Werte zurückgeben:
|
|
Auch hier anfordern Sie die Halterung von einer Testfunktion, indem Sie den Namen der Halterung als Parameter angeben:
|
|
Konfigurationsdateien
pytest
kann seine projektspezifische Konfiguration aus einer dieser Dateien lesen:
pytest.ini
tox.ini
setup.cfg
Welche Datei Sie verwenden sollten, hängt davon ab, welche anderen Werkzeuge Sie in Ihrem Projekt verwenden. Wenn Sie Ihr Projekt gepackt haben, sollten Sie die Datei setup.cfg
verwenden. Wenn Sie Tox verwenden, um Ihren Code in verschiedenen Umgebungen zu testen, können Sie die pytest
-Konfiguration in die Datei tox.ini
eintragen. Die Datei pytest.ini
kann verwendet werden, wenn Sie außer pytest
kein weiteres Tool verwenden möchten.
Die Konfigurationsdatei sieht für jeden dieser drei Dateitypen weitgehend gleich aus:
pytest.ini und tox.ini verwenden
|
|
Wenn Sie die Datei setup.cfg verwenden, besteht der einzige Unterschied darin, dass Sie dem Abschnitt [pytest]
das Wort tool:
voranstellen müssen, etwa so:
|
|
conftest.py
Jeder Ordner mit Testdateien kann eine Datei conftest.py
enthalten, die von pytest
gelesen wird. Dies ist ein guter Ort, um Ihre benutzerdefinierten Fixtures zu speichern, da diese von verschiedenen Testdateien gemeinsam genutzt werden können.
Die Datei(en) conftest.py
können das Verhalten von pytest
projektspezifisch verändern.
Neben gemeinsam genutzten Fixtures können Sie auch externe Hooks und Plugins oder Modifikatoren für den PATH
platzieren, die von pytest
verwendet werden, um Tests und Implementierungscode zu finden.
CLI / PDB
Während der Entwicklung, vor allem wenn Sie Ihre Tests vor der Implementierung schreiben, kann pytest
ein nützliches Werkzeug zum Debuggen sein.
Wir werden uns die nützlichsten Befehlszeilenoptionen ansehen.
Nur einen Test ausführen
Wenn Sie nur einen bestimmten Test ausführen möchten, können Sie diesen Test über die Datei test_
, in der er sich befindet, und den Namen der Funktion referenzieren:
|
|
Nur sammeln
Manchmal möchten Sie nur eine Liste der Testsammlung haben, anstatt alle Testfunktionen auszuführen.
|
|
Beenden beim ersten Fehler
Sie können pytest
zwingen, nach einem fehlgeschlagenen Test keine weiteren Tests mehr auszuführen:
|
|
Nur den letzten fehlgeschlagenen Test ausführen
Wenn Sie nur die Tests ausführen möchten, die beim letzten Mal fehlgeschlagen sind, können Sie dies mit dem Flag --lf
tun:
|
|
Führen Sie alle Tests durch, aber die zuletzt fehlgeschlagenen Tests zuerst.
|
|
Werte von lokalen Variablen in der Ausgabe anzeigen
Wenn wir eine komplexere Testfunktion mit einigen lokalen Variablen einrichten, können wir pytest
anweisen, diese lokalen Variablen mit dem Flag -l
anzuzeigen.
Lassen Sie uns unsere Testfunktion so umschreiben:
|
|
Dann,
|
|
erhalten wir diese Ausgabe:
|
|
Verwendung von pytest mit einem Debugger
Es gibt einen Kommandozeilen-Debugger namens pdb
, der in Python integriert ist. Sie können pytest
verwenden, um den Code Ihrer Testfunktion zu debuggen.
Wenn Sie pytest
mit --pdb
starten, wird eine pdb
Debugging-Sitzung gestartet, sobald in Ihrem Test eine Ausnahme ausgelöst wird. In den meisten Fällen ist dies nicht besonders nützlich, da Sie vielleicht jede Codezeile vor der ausgelösten Ausnahme untersuchen möchten.
Eine weitere Option ist das --trace
Flag für pytest
, das einen Haltepunkt bei der ersten Zeile jeder Testfunktion setzt. Dies kann etwas unpraktisch werden, wenn Sie viele Tests haben. Für Debugging-Zwecke ist daher eine gute Kombination --lf --trace
, die eine Debug-Sitzung mit pdb
am Anfang des letzten fehlgeschlagenen Tests starten würde:
|
|
CI / CD
In modernen Softwareprojekten wird die Software nach den Grundsätzen der Test Driven Development (testgetriebene Entwicklung) entwickelt und über eine Continuous Integration / Continuous Deployment-Pipeline mit automatisierten Tests ausgeliefert.
Typischerweise werden Übertragungen an den main/master-Zweig abgelehnt, wenn nicht alle Testfunktionen erfolgreich sind.
Wenn Sie mehr über die Verwendung von pytest
in einer CI/CD-Umgebung erfahren möchten, bleiben Sie dran, denn ich plane einen neuen Artikel zu diesem Thema.
Dokumentation
Die offizielle Dokumentation für pytest
finden Sie hier: https://docs.pytest.org
Eine kurze Anmerkung:
Dies ist eine Wiederveröffentlichung des Originalartikels von Bas Steins mit seiner Genehmigung. Besuchen Sie seine Website für weitere Artikel und/oder folgen Sie ihm auf Twitter: @bascodes