mitmario.dev

Warum Tests

Python Sandbox 3 Min Lesezeit 4 BeispieleLektion 1 von 6

Du änderst etwas an einem Programm, und danach geht es an einer Stelle kaputt, an die du beim Ändern gar nicht gedacht hast. Das passiert öfter, als einem lieb ist. Dagegen helfen Tests, also kleine Programme, die ein anderes Programm prüfen.

Zwei davon laufen in diesem Kurs schon die ganze Zeit mit. Das eine ist die Prüfliste unter jeder Aufgabe, das andere seit Abschnitt 8 die pruefen.py im Dateibaum, die deine Funktionen aufruft und hinschreibt, was zurückkommt. Ab hier schreibst du Tests selbst.

Von Hand nachsehen

Von Hand nachgesehen
from porto import porto

print(porto(500))
print(porto(2000))
print(porto(2001))
print(porto(12000))

porto() ist die Porto-Tabelle aus Abschnitt 3 als Funktion. Diesmal rechnet sie in Gramm, weil eine Waage Gramm anzeigt. Bis 2 kg gibt es ein Päckchen für 3,99 Euro, bis 10 kg ein Paket für 5,99 Euro und bis 31,5 kg ein großes für 9,99 Euro. Was darüber liegt, kommt später in diesem Abschnitt dran. main.py ruft die Funktion mit vier Gewichten auf und gibt aus, was zurückkommt.

So prüft man am Anfang fast immer. Laufen lassen und hinsehen. Das geht, solange man weiß, was dastehen muss. Ob die zweite Zeile stimmt, sieht nur, wer die Tabelle im Kopf hat und weiß, dass 2000 Gramm noch ein Päckchen sind. Und nach jeder Änderung an porto() geht das wieder von vorn los, mit allen vier Zeilen.

Eine Weile geht das gut. Irgendwann ändert man etwas, sieht nur auf die Zeile, um die es gerade ging, und die daneben ist seit zwei Wochen falsch.

assert

Dieselben Fälle mit assert
from porto import porto

assert porto(500) == 3.99
assert porto(2000) == 3.99
assert porto(2001) == 5.99
assert porto(12000) == 9.99
print("Alle Prüfungen bestanden")

assert (behaupten) nimmt einen Ausdruck, der wahr oder falsch ist, so wie die Bedingung eines if. Ist er wahr, passiert nichts, und Python macht mit der nächsten Zeile weiter. Hier sind alle vier wahr, und am Ende steht nur Alle Prüfungen bestanden. Was herauskommen muss, steht jetzt in der Datei und nicht mehr in deinem Kopf. Das Nachsehen erledigt Python.

Die Datei heißt test_porto.py und liegt neben porto.py. Funktion und assert-Zeilen stehen also getrennt, und die Testdatei holt sich die Funktion mit import wie jedes andere Programm. So bleibt porto.py, wie es ist, und test_porto.py lässt sich starten, wann immer man will. Das test_ vorn im Namen ist kein Zufall. Dazu mehr in der nächsten Lektion.

Wenn eine assert-Zeile anschlägt

Eine assert-Zeile schlägt an
from porto import porto

assert porto(500) == 3.99
assert porto(2000) == 3.99
assert porto(2001) == 5.99
assert porto(12000) == 9.99
print("Alle Prüfungen bestanden")

Jemand hat porto() aufgeräumt und dabei aus <= ein < gemacht. Die assert-Zeilen sind dieselben, und diesmal endet der Lauf mit einem Traceback. Die Fundstelle ist Zeile 4 in test_porto.py. Darunter steht die Zeile selbst, assert porto(2000) == 3.99, und die letzte Zeile heißt nur AssertionError.

Diesen Fehler löst Python aus, wenn der Ausdruck hinter assert falsch ist. Weil ihn niemand abfängt, endet das Programm mit dem Exit-Code 1. Bis zu Alle Prüfungen bestanden kommt es nicht.

Mehr erfährst du nicht. Was porto(2000) stattdessen zurückgegeben hat, steht nirgends, und die assert-Zeilen nach der vierten laufen gar nicht mehr. Beides kann pytest besser, darum geht es in der nächsten Lektion.

Wichtiger ist, welche Zeile angeschlagen hat. 500 Gramm liegen weit weg von jeder Grenze und kosten mit < dasselbe wie mit <=. Den Fehler bemerkt nur die Zeile mit genau 2000 Gramm. Daraus folgen zwei Regeln für Tests.

Die erste ist, dass mit dem genauen Ergebnis verglichen wird, also == 3.99, und nicht mit etwas wie > 0, das auch ein falscher Preis erfüllt. Die zweite ist, dass an jeder Grenze zwei assert-Zeilen stehen. Eine prüft den letzten Wert, der noch dazugehört, die andere den ersten, der nicht mehr dazugehört. Hier sind das 2000 und 2001 Gramm, bei Geld wären es zwei Beträge, die einen Cent auseinanderliegen.

Eine assert-Zeile, die nie anschlägt

Eine assert-Zeile, die nie anschlägt
from porto import porto

assert porto(500) == 3.99
assert (porto(2000) == 3.99, "2 kg sind noch ein Päckchen")
assert porto(2001) == 5.99
assert porto(12000) == 9.99
print("Alle Prüfungen bestanden")

Derselbe kaputte Code, und trotzdem steht da Alle Prüfungen bestanden. Der Fehler dieser Lektion steckt in Zeile 4. Um den Ausdruck und die Meldung dahinter stehen Klammern, so wie man es von print kennt.

assert ist aber keine Funktion. Was in den Klammern steht, ist ein Tupel aus zwei Werten (Lektion 5.7). Falsch sind nur False, None, die Null und alles Leere (Lektion 3.5), und dieses Tupel ist nicht leer. Damit ist die Zeile immer wahr, ganz egal, was porto(2000) zurückgibt.

Python merkt das sogar und schreibt auf den Fehlerkanal SyntaxWarning: assertion is always true, perhaps remove parentheses?. Auf Deutsch heißt das, die Behauptung ist immer wahr, und vielleicht gehören die Klammern weg. Eine Warnung ist aber kein Abbruch. Das Programm läuft weiter.

Richtig heißt die Zeile assert porto(2000) == 3.99, "2 kg sind noch ein Päckchen", ohne Klammern. Der Text nach dem Komma steht beim Anschlagen hinter AssertionError.

Dass eine assert-Zeile durchläuft, sagt also nichts darüber, ob sie etwas prüft. Eine kaputte läuft genauso durch. Das siehst du erst, wenn du absichtlich einen Fehler einbaust und sie dann anschlägt, so wie die richtige Zeile im dritten Beispiel.

Was die Aufgabe will

Die Versandregel aus Abschnitt 3 bekommt jetzt assert-Zeilen von dir. Danach macht pruefen.py damit dasselbe wie im dritten Beispiel. Nacheinander werden Fehler in die Funktion eingebaut, und jedes Mal wird geschaut, ob deine assert-Zeilen sie bemerken.

Zum Mitnehmen

Ein Test ist ein kleines Programm, das ein anderes prüft. Stimmt der Ausdruck hinter assert, passiert nichts, sonst bricht das Programm mit einem AssertionError ab. Gute assert-Zeilen vergleichen mit dem genauen Ergebnis, prüfen jede Grenze von beiden Seiten und schlagen an, wenn man mit Absicht einen Fehler einbaut.

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 4 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.