mitmario.dev

Tests für das Werkzeug

Python Sandbox 4 Min Lesezeit 4 BeispieleLektion 5 von 8

Drei Teile des Werkzeugs stehen, und sie rechnen richtig. Heute jedenfalls. Nächsten Monat kommt ein neuer Laden dazu und mit ihm eine neue Regel. Im Frühjahr wechselt vielleicht die Bank, und irgendwann baut man am Einlesen etwas um, weil eine Spalte anders heißt. Jedes Mal soll der Rest weiter stimmen, und das von Hand nachzurechnen, macht niemand zweimal.

Wie Tests mit pytest gehen, steht in Abschnitt 15. Hier geht es darum, welche Tests ein Werkzeug wie dieses braucht und woher ihre Daten kommen.

Was man testet

Nicht alles ist gleich wichtig. Getestet wird zuerst, was sich ändern wird, und das, was schiefgeht, ohne dass man es sieht:

  • Die Regeln. Sie wachsen jeden Monat, und eine Regel an der falschen Stelle sortiert still falsch ein. Ein Test je Eigenschaft: ein Treffer im zweiten Feld, Groß und klein, eine Buchung, auf die zwei Regeln passen, und eine, auf die keine passt.
  • Das Einlesen fremder Dateien. Beide Kodierungen und eine Zeile, die kaputt ist. Hier kommen die Daten von außen, und von außen kommt irgendwann alles.
  • Die Summen. Vor allem die Stelle, an der Einnahmen und Ausgaben getrennt werden, denn ein Fehler dort ergibt Zahlen, die plausibel aussehen.

Nicht getestet wird, wie die Tabelle von tabulate aussieht. Die zeichnet ein Paket, das seine eigenen Tests hat. Ein Test, der jeden Strich der Tabelle vergleicht, wird außerdem rot, sobald eine Kategorie einen längeren Namen bekommt, obwohl alles stimmt. Prüfen lohnt sich, was in der Tabelle steht, und das geht mit in.

Kleine Daten im Test

Drei Tests für die Ordnerregeln
from postfach import ordner_fuer

REGELN = {"rechnung": "Rechnungen", "TSV": "Verein", "versandhaus": "Einkäufe"}


def test_stichwort_im_betreff():
    assert ordner_fuer("service@versandhaus-elster.example", "Ihre Rechnung", REGELN) == "Rechnungen"


def test_gross_geschriebene_regel():
    assert ordner_fuer("info@tsv-lindenau.example", "Einladung", REGELN) == "Verein"


def test_keine_regel_passt():
    assert ordner_fuer("mia.berger@example.org", "Fotos vom Sommerfest", REGELN) == "Posteingang"

Die Tests für die Ordnerregeln bringen ihre Regeln selbst mit, drei Stück in einem Dictionary. Die Datei ordner.csv lesen sie nicht. Der Grund ist einfach. Die Datei soll sich ändern dürfen, ohne dass ein Test rot wird, und in jedem Test sollen nur die Regeln stehen, auf die es bei ihm ankommt.

Wenn du später wissen willst, warum test_gross_geschriebene_regel rot ist, siehst du die Regel TSV und die Mail gleich daneben. Nachsehen musst du in keiner Datei.

Jeder Test prüft eine Eigenschaft, und welche, steht schon in seinem Namen. Wird einer rot, steht der Name in der Zeile von pytest, und man weiß ohne Lesen, was kaputt ist.

Ein Ordner nur für den Test

Ein Ordner nur für den Test
from lesen import lies_text


def test_utf8(tmp_path):
    datei = tmp_path / "liste.csv"
    datei.write_text("Nachname;Vorname\nKrüger;Tim\n", encoding="utf-8")
    assert lies_text(datei) == ("Nachname;Vorname\nKrüger;Tim\n", "utf-8")


def test_cp1252(tmp_path):
    datei = tmp_path / "liste.csv"
    datei.write_text("Nachname;Vorname\nKrüger;Tim\n", encoding="cp1252")
    assert lies_text(datei) == ("Nachname;Vorname\nKrüger;Tim\n", "cp1252")

Fürs Einlesen braucht ein Test eine Datei. Die soll er sich selbst schreiben, damit es egal ist, was gerade im Ordner liegt. Dafür gibt es tmp_path.

Steht dieses Wort als Parameter in der Testfunktion, bekommt sie von pytest einen eigenen, leeren Ordner als Path, für jeden Test einen neuen. Der Test schreibt dort seine Datei, mit genau der Kodierung, um die es geht, und ruft danach die Funktion damit auf.

Woher der Ordner kommt, muss man nicht wissen. Er liegt bei den vorübergehenden Dateien des Systems, unter Linux und hier in /tmp, und alte Ordner räumt pytest von selbst weg.

So ein Parameter, den pytest von selbst füllt, heißt Fixture. tmp_path ist die, die man am häufigsten braucht, und für dieses Werkzeug die einzige.

Ein Test, der nächsten Monat rot wird

Ein Test mit der echten Liste
from lesen import lies_text


def test_die_echte_liste():
    text, kodierung = lies_text("mitglieder.csv")
    zeilen = text.splitlines()
    assert len(zeilen) == 8

Dieser Fehler ist bequem. Der Test liest die echte mitglieder.csv und prüft, ob sie acht Zeilen hat. Als er geschrieben wurde, stimmte das. Dann ist Mia in den Verein eingetreten, und der Test ist rot: assert 9 == 8. An lies_text hat sich nichts geändert. Der Test meldet einen Fehler, den es gar nicht gibt.

Mit dem Kontoauszug passiert das jeden Monat, weil jeder Monat eine neue Datei bringt. Ein Test, der mit dem echten Export rechnet, ist am Ersten rot, und nach dem dritten Mal nimmt ihn niemand mehr ernst.

Die Daten eines Tests kommen deshalb aus dem Test selbst. Ob das Werkzeug mit dem echten Export klarkommt, merkst du beim Benutzen.

Ein Fehler zur Probe

Ein Fehler zur Probe
from postfach import ordner_fuer

REGELN = {"rechnung": "Rechnungen", "TSV": "Verein", "versandhaus": "Einkäufe"}


def test_stichwort_im_betreff():
    assert ordner_fuer("service@versandhaus-elster.example", "Ihre Rechnung", REGELN) == "Rechnungen"


def test_gross_geschriebene_regel():
    assert ordner_fuer("info@tsv-lindenau.example", "Einladung", REGELN) == "Verein"


def test_keine_regel_passt():
    assert ordner_fuer("mia.berger@example.org", "Fotos vom Sommerfest", REGELN) == "Posteingang"

Ob ein Test etwas bringt, sieht man wie in Abschnitt 15 erst, wenn man einen Fehler einbaut. Hier steht in postfach.py die Fassung aus Lektion 19.3, in der erst die Felder und dann die Regeln durchgegangen werden. Der Test mit der Rechnung vom Versandhaus wird rot, und in seiner Meldung steht, welcher Ordner herauskam.

Ohne diesen Test wäre die Umstellung durchgegangen. Die Rechnung läge seitdem bei den Einkäufen.

Was die Aufgabe will

In test_auswerter.py kommen Tests für das ganze Werkzeug, mindestens sechs, für das Einlesen, für die Regeln und für die Übersicht. Beim Prüfen werden danach Fehler in die drei Teile eingebaut, und jeder muss einem deiner Tests auffallen.

Zum Mitnehmen

Zuerst bekommen die Teile Tests, die sich ändern werden, also die Regeln, das Lesen fremder Dateien und die Summen. Jeder Test bringt seine eigenen kleinen Daten mit, in denen genau sein Fall steckt, und liest nicht die echte Datei, die im nächsten Monat schon anders aussieht. Braucht ein Test eine Datei, legt er sie in tmp_path an, einem Ordner, den pytest für jeden Test frisch bereitstellt.

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.