mitmario.dev

Erst der Test, dann der Code

Python Sandbox 3 Min Lesezeit 4 BeispieleLektion 5 von 6

Eine Funktion für Rechnungsnummern soll RE-2026-0042 liefern. Es gibt sie noch nicht, aber drei Tests stehen schon da, und in denen steht, was sie können muss. Bis jetzt kam der Test immer nach dem Code. Die Funktion stand da, und die Tests hielten fest, was sie tut. Hier geht es andersherum. Das klingt umständlich, aber der Vorteil zeigt sich schnell, weil du dich entscheiden musst, was herauskommen soll, bevor du darüber nachdenkst, wie.

Rot

Rot, die Tests vor dem Code
import pytest

from rechnung import rechnungsnummer


def test_vierstellig_mit_nullen():
    assert rechnungsnummer(2026, 42) == "RE-2026-0042"


def test_lange_nummern_bleiben_ganz():
    assert rechnungsnummer(2026, 12345) == "RE-2026-12345"


def test_null_gibt_es_nicht():
    with pytest.raises(ValueError, match="fangen bei 1 an"):
        rechnungsnummer(2026, 0)

Gebraucht wird eine Funktion für Rechnungsnummern wie RE-2026-0042. Erst das Jahr, dann die laufende Nummer mit mindestens vier Stellen, vorn mit Nullen aufgefüllt. Längere Nummern bleiben ganz, und gezählt wird ab 1. Jeder der drei Tests prüft einen Teil davon.

In rechnung.py steht dagegen nur die erste Zeile der Funktion samt Docstring. Das ist gültiges Python. Die Funktion tut nur nichts und gibt None zurück.

Also sind alle drei rot. Beim ersten steht assert None == 'RE-2026-0042', beim zweiten dasselbe mit 'RE-2026-12345' und beim dritten DID NOT RAISE ValueError. Das ist der erste Schritt, und er gehört dazu. Ein Test, der grün ist, bevor es den Code gibt, prüft nichts, das kennst du aus den letzten Lektionen.

Rot sein muss er außerdem aus dem richtigen Grund. Fehlt die Funktion in rechnung.py ganz, kommt pytest gar nicht erst zu den Tests und meldet beim Einsammeln ImportError: cannot import name 'rechnungsnummer'.

Grün

Grün, gerade genug
import pytest

from rechnung import rechnungsnummer


def test_vierstellig_mit_nullen():
    assert rechnungsnummer(2026, 42) == "RE-2026-0042"


def test_lange_nummern_bleiben_ganz():
    assert rechnungsnummer(2026, 12345) == "RE-2026-12345"


def test_null_gibt_es_nicht():
    with pytest.raises(ValueError, match="fangen bei 1 an"):
        rechnungsnummer(2026, 0)

Die Funktion bekommt gerade so viel Code, dass die Tests grün werden. Erst die Ablehnung für Nummern unter 1, dann die Nummer als Text, und vorn kommt so lange eine Null davor, bis sie vier Zeichen hat. Schön ist das nicht. Aber 3 passed.

„Gerade genug“ heißt auch, dass die Funktion noch nicht kann, was kein Test verlangt. Soll sie später etwa ein Jahr vor 2000 ablehnen, kommt zuerst ein Test dafür und erst dann der Code.

Aufräumen

Aufräumen mit Netz
import pytest

from rechnung import rechnungsnummer


def test_vierstellig_mit_nullen():
    assert rechnungsnummer(2026, 42) == "RE-2026-0042"


def test_lange_nummern_bleiben_ganz():
    assert rechnungsnummer(2026, 12345) == "RE-2026-12345"


def test_null_gibt_es_nicht():
    with pytest.raises(ValueError, match="fangen bei 1 an"):
        rechnungsnummer(2026, 0)

Die Schleife lässt sich durch einen f-String ersetzen, und genau dafür sind die Tests da. Der erste Versuch, {nummer:4d}, macht einen davon sofort rot.

Bei Text zeigt pytest, wo sich die beiden Seiten unterscheiden. In der Zeile mit dem Minus steht, was erwartet war, RE-2026-0042. In der mit dem Plus steht, was herauskam, RE-2026- 42, und die Dächer darunter zeigen auf die Stelle. 4d füllt mit Leerzeichen auf, nicht mit Nullen.

Aufgeräumt
import pytest

from rechnung import rechnungsnummer


def test_vierstellig_mit_nullen():
    assert rechnungsnummer(2026, 42) == "RE-2026-0042"


def test_lange_nummern_bleiben_ganz():
    assert rechnungsnummer(2026, 12345) == "RE-2026-12345"


def test_null_gibt_es_nicht():
    with pytest.raises(ValueError, match="fangen bei 1 an"):
        rechnungsnummer(2026, 0)

Mit {nummer:04d} sind wieder alle drei grün. Die 0 vor der 4 füllt vorn mit Nullen auf, auf mindestens vier Stellen, und was länger ist, bleibt ganz. Zwischen den beiden Versuchen lag ein einziger Lauf von pytest, und der hat den Fehler gefunden, bevor ihn jemand auf einer Rechnung gesehen hat. Das ist mit „aufräumen mit Netz“ gemeint. Du kannst den Code ohne Angst ändern, weil du nach jedem Schritt siehst, ob er noch tut, was er soll.

Der Fehler, der in diesem Schritt am nächsten liegt, ist kein Fehler in Python. Ein Test ist rot, und statt den Code zu reparieren, ändert man den Test, bis er zum Code passt, hier also auf RE-2026- 42. Dann ist alles grün. Nur beschreiben die Tests jetzt nicht mehr, was die Funktion tun soll, sondern bloß noch, was sie gerade tut.

Wann sich das lohnt

Dieser Kreis heißt rot, grün, aufräumen, und das ganze Vorgehen testgetriebene Entwicklung (test-driven development, kurz TDD). Wenn du den Kreis eine Weile gefahren bist, merkst du, dass die Tests dabei mehr sind als eine Kontrolle. Sie sind die genaueste Beschreibung der Funktion, die es gibt. Nötig ist das nicht bei jeder Funktion. Bei allem, was mit Geld oder mit Daten anderer Leute zu tun hat, lohnt es sich.

Was die Aufgabe will

In test_iban.py stehen fünf fertige Tests für eine Funktion iban_formatieren(), die eine IBAN in Vierergruppen schreibt. Von der Funktion steht in iban.py nur die erste Zeile samt Docstring. Den Rest schreibst du, bis alle fünf grün sind. Danach ruft pruefen.py sie noch mit IBANs auf, die in keinem der Tests stehen. Fünf Tests sind eben nie alles.

Zum Mitnehmen

Beim Kreis aus Rot, Grün und Aufräumen stehen zuerst die Tests für das da, was eine Funktion können soll, und alle sind rot. Danach reicht knapp genug Code, um sie grün zu machen. Beim Aufräumen zeigen dieselben Tests nach jedem Schritt, ob noch alles stimmt.

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.