mitmario.dev

pytest

Python Sandbox 4 Min Lesezeit 3 BeispieleLektion 2 von 6

Eine Datei voller assert hört beim ersten Fehler auf. Was danach kommt, läuft nicht mehr. Und an der Zeile, die anschlägt, steht nur AssertionError, aber nicht, was stattdessen herauskam. Beides macht pytest besser, ein Paket von PyPI und das Werkzeug, mit dem dieser Abschnitt arbeitet. Hier liegt es bereit. Zu Hause installierst du es mit python -m pip install pytest, am besten in der Umgebung des Projekts (Lektion 13.3).

Tests als Funktionen

Vier Tests für das Porto
from porto import porto


def test_leichtes_paeckchen():
    assert porto(500) == 3.99


def test_paeckchen_an_der_grenze():
    assert porto(2000) == 3.99


def test_paket_knapp_darueber():
    assert porto(2001) == 5.99


def test_grosses_paket():
    assert porto(12000) == 9.99

Das sind dieselben vier assert-Zeilen wie in der letzten Lektion, nur steht jetzt jede in einer eigenen Funktion. Ihr Name fängt mit test_ an, und die Datei, in der sie stehen, heißt ebenfalls test_ und dann irgendwas. Mehr braucht es nicht, auch wenn pytest noch ein paar andere Schreibweisen kennt, etwa porto_test.py.

pytest sucht im Ordner nach solchen Dateien, liest sie ein und ruft jede Funktion mit test_ vorn auf, jede für sich. Aufrufen musst du sie nicht. Innen steht ein ganz gewöhnliches assert.

Die Ausgabe ist kurz. Ein Punkt je grünem Test, ganz rechts [100%], darunter 4 passed. Die Zeit dahinter ist bei jedem Lauf eine andere. Der Name eines Tests darf ruhig lang sein und sagen, welcher Fall geprüft wird. Wenn der Test einmal rot wird, liest du ihn als Erstes.

Zu Hause startest du pytest im Ordner des Projekts mit python -m pytest -q. Dieser Befehl steht hier auch in der Befehlsleiste. -q (quiet, leise) lässt oben die Angaben zu Python und pytest weg. Mit python -m pytest ohne -q, ebenfalls in der Befehlsleiste, siehst du sie. python -m steht aus demselben Grund davor wie bei pip in Lektion 13.1, so nimmt man das pytest, das zu diesem Python gehört.

Bei einem Beispiel wird hier immer eine Datei gestartet. Deshalb liegt daneben eine kleine pruefen.py, die mit pytest.main(["-q"]) dasselbe tut wie der Befehl. In den Aufgaben startet pruefen.py pytest genauso und macht danach noch etwas mehr.

Ein Test wird rot

Ein Test wird rot
from porto import porto


def test_leichtes_paeckchen():
    assert porto(500) == 3.99


def test_paeckchen_an_der_grenze():
    assert porto(2000) == 3.99


def test_paket_knapp_darueber():
    assert porto(2001) == 5.99


def test_grosses_paket():
    assert porto(12000) == 9.99

Der Fehler aus der letzten Lektion ist wieder drin, aus <= wurde <. In der ersten Zeile steht .F... Der zweite Test ist rot, F für failed, und die beiden danach sind trotzdem gelaufen.

Darunter, unter FAILURES, kommt der rote Test im Einzelnen. Die Zeile mit dem > davor ist die, an der es passiert ist. In den Zeilen mit E steht, was los war: assert 5.99 == 3.99, und darunter where 5.99 = porto(2000). Herausgekommen sind 5.99, erwartet waren 3.99, und die 5.99 stammen aus dem Aufruf porto(2000).

Dafür musst du nichts tun. pytest sieht sich jedes assert einer Testdatei an, bevor es sie laufen lässt, und merkt sich die Werte auf beiden Seiten.

Ganz unten steht in einer Zeile, was rot ist, mit dem Namen der Datei und des Tests. Die letzte Zeile zählt: 1 failed, 3 passed. Außerdem endet pytest mit einem Exit-Code, den python -m pytest -q; echo $? aus der Befehlsleiste zeigt. Er ist 0, wenn alles grün ist, 1, wenn mindestens ein Test rot ist, und 5, wenn pytest gar keinen Test gefunden hat.

Im letzten Fall steht dort nur no tests ran. Das passiert, wenn die Namen nicht passen, denn eine Funktion pruefe_porto() ist für pytest eine Funktion wie jede andere. Die Aufgabe dieser Lektion fängt so an, mit einer Datei ohne Test.

Ein Test ohne assert

Ein Test ohne assert
from porto import porto


def test_leichtes_paeckchen():
    assert porto(500) == 3.99


def test_paeckchen_an_der_grenze():
    porto(2000) == 3.99


def test_paket_knapp_darueber():
    assert porto(2001) == 5.99


def test_grosses_paket():
    assert porto(12000) == 9.99

Derselbe Fehler steckt in porto(), und trotzdem steht da 4 passed. Im zweiten Test fehlt das assert. Die Zeile porto(2000) == 3.99 wird zwar ausgerechnet, und das Ergebnis ist False. Aber es geht nirgends hin, so wie bei einer Zeile, in der nur 3 + 4 steht. Für pytest ist ein Test grün, wenn die Funktion ohne Fehler zu Ende läuft. Und das tut sie.

Das ist der Fehler dieser Lektion. Er ist gemeiner als die Klammern hinter assert, weil hier nicht einmal Python warnt. Ein Test ohne assert prüft nichts und steht trotzdem genauso grün in der Liste wie einer, der etwas prüft. Auffallen tut er nur auf die Art aus der letzten Lektion, wenn du einen Fehler einbaust und der Test trotzdem grün bleibt.

Was die Aufgabe will

Zwei Tests für eine Funktion rabatt(), die einen Preis um ein paar Prozent senkt. pruefen.py startet pytest. Wenn alles grün ist, werden nacheinander zwei Fehler in die Funktion eingebaut, und bei jedem muss einer deiner Tests rot werden.

Zum Mitnehmen

pytest sammelt Dateien und Funktionen ein, deren Name mit test_ beginnt, und führt jede Funktion als eigenen Test aus. Darin reicht ein schlichtes assert, und wird ein Test rot, stehen beide Seiten des Vergleichs in der Ausgabe. Ohne assert prüft eine Testfunktion gar nichts und gilt trotzdem als passed.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.