Abschnitt 15 · Lektion 1
Warum Tests
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
def porto(gramm):
"""Was ein Paket kostet, nach seinem Gewicht in Gramm."""
if gramm <= 2000:
return 3.99
elif gramm <= 10000:
return 5.99
elif gramm <= 31500:
return 9.99
else:
raise ValueError("Zu schwer, das geht nur als Spedition") 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
def porto(gramm):
"""Was ein Paket kostet, nach seinem Gewicht in Gramm."""
if gramm <= 2000:
return 3.99
elif gramm <= 10000:
return 5.99
elif gramm <= 31500:
return 9.99
else:
raise ValueError("Zu schwer, das geht nur als Spedition") 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
def porto(gramm):
"""Was ein Paket kostet, nach seinem Gewicht in Gramm."""
if gramm < 2000:
return 3.99
elif gramm <= 10000:
return 5.99
elif gramm <= 31500:
return 9.99
else:
raise ValueError("Zu schwer, das geht nur als Spedition") 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
def porto(gramm):
"""Was ein Paket kostet, nach seinem Gewicht in Gramm."""
if gramm < 2000:
return 3.99
elif gramm <= 10000:
return 5.99
elif gramm <= 31500:
return 9.99
else:
raise ValueError("Zu schwer, das geht nur als Spedition") 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, kostenlosZu 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.