Einstieg ins Testen

Der erste Test gehört an den letzten Fehler

In einer gewachsenen Anwendung entscheidet nicht die Testtechnik über den Nutzen, sondern die Auswahl: Wenige Tests an den richtigen Stellen tragen mehr als hundert an den leichten.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Seit 1997 am Markt Kleine Gruppen Präsenz und Live-Online Zertifizierte Trainer
Worum es geht

Ohne Auswahl endet der Anlauf nach zwei Wochen

Der übliche Start: Es wird mit dem Verzeichnis für Hilfsfunktionen begonnen, weil das ohne Umbau testbar ist. Nach zwei Wochen gibt es zweihundert Tests, die prüfen, dass eine Zeichenkette gekürzt und ein Datum formatiert wird. Die Abdeckung steigt sichtbar, die Fehler im Betrieb bleiben, weil sie in der Preisberechnung und im Zustandswechsel des Bestellvorgangs entstehen, und dort ist noch nichts getestet.

Der zweite verbreitete Fehlstart ist die Zielvorgabe. Wird eine Abdeckungszahl vereinbart, bevor die ersten sinnvollen Tests existieren, entstehen Tests, die Code ausführen, ohne etwas zu prüfen. Solche Tests erfüllen die Vorgabe und sagen nichts aus. Wenn sie später brechen, versteht niemand ihre Absicht, also werden sie übersprungen, und aus dem übersprungenen Test wird ein dauerhafter Zustand.

Was diese Anläufe wirklich kostet, ist nicht die investierte Zeit, sondern die Schlussfolgerung daraus. Nach dem zweiten gescheiterten Versuch gilt im Team als ausgemacht, dass sich diese Anwendung nun einmal nicht testen lässt. Danach wird jede Änderung wieder von Hand durchgeklickt, und größere Umbauten unterbleiben, weil sich niemand traut.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Schritt für Schritt

Der Einstieg in sieben Schritten

Die Reihenfolge ist so gewählt, dass nach jedem Schritt etwas Brauchbares vorliegt. Wenn der Anlauf nach Schritt drei unterbrochen wird, hast du trotzdem einen Test, der einen echten Fehler festhält.

  1. 1

    Den letzten echten Fehler heraussuchen

    Nimm einen Fehler aus dem Betrieb, für den du die Eingabedaten von damals noch bekommst. Schreib in einem Satz auf, was hätte passieren sollen und was passiert ist. Das ist bereits die Beschreibung deines ersten Tests.

    Geschafft, wenn: ein Fall mit konkreten Eingaben und einem erwarteten Ergebnis, formuliert ohne Code

  2. 2

    Die Testumgebung minimal aufsetzen

    Ein Testwerkzeug, eine Konfigurationsdatei, ein Eintrag in den Skripten. Für Code ohne DOM genügt die Node-Umgebung, für Komponenten kommt jsdom oder happy-dom dazu. Widersteh der Versuchung, gleich Abdeckungsberichte, Momentaufnahmen und einen Pipeline-Schritt mit einzurichten.

    Geschafft, wenn: ein einziger Befehl, der ohne Zusatzargumente läuft und einen bewusst grünen Test meldet

  3. 3

    Den Fehler als roten Test nachstellen

    Schreib den Test so, dass er mit dem aktuellen Code fehlschlägt. Wenn er nicht fehlschlägt, hast du den Fehler noch nicht verstanden oder triffst eine andere Stelle als gedacht. Erst wenn er rot ist, folgt die Korrektur.

    Geschafft, wenn: ein Test, der ohne die Korrektur fehlschlägt und mit ihr durchläuft

  4. 4

    Eine Naht in den Code schneiden

    Zieh die Regel aus ihrer Umgebung heraus: die Berechnung aus dem Ereignisrückruf, die Umwandlung aus der Anzeige. Übergib der neuen Funktion alles, was sie braucht, statt es sich holen zu lassen. Ein Standardwert am Parameter lässt bestehende Aufrufe unverändert.

    Geschafft, wenn: eine Funktion, die aus ihren Eingaben ein Ergebnis macht, ohne auf Globales zuzugreifen

  5. 5

    Zeit, Zufall und Netzwerk stillstellen

    Ersetz neue Date-Objekte durch eine übergebene Uhr oder eine Zeitsteuerung des Testwerkzeugs, Zufall durch einen festen Wert und HTTP-Aufrufe durch eine Attrappe auf Protokollebene. Damit hängt das Ergebnis nicht mehr am Tag, an dem der Test läuft.

    Geschafft, wenn: zwei Läufe mit identischem Ergebnis, auch am Monatsletzten und um 23:59 Uhr

  6. 6

    Randfälle ergänzen

    Zu jeder Regel gehören die Grenze selbst, ein Wert knapp darunter und darüber, der leere Fall und der Fehlerfall. Ein Test pro Regel reicht, wenn er die Grenze trifft, und zehn Tests reichen nicht, wenn sie alle die Mitte prüfen.

    Geschafft, wenn: je Regel mindestens ein Test, der die Grenze prüft und nicht den bequemen Fall

  7. 7

    Bei jedem Push laufen lassen

    Die Tests müssen automatisch laufen, sonst werden sie beim ersten Zeitdruck übergangen. Ein roter Lauf blockiert das Zusammenführen. Übersprungene Tests bekommen eine Frist, sonst wachsen sie zu einem stillen Bestand heran, den niemand mehr anfasst.

    Geschafft, wenn: ein roter Lauf verhindert die Zusammenführung, statt in einer Liste zu landen

Fünf Kriterien, die die Reihenfolge festlegen

  1. 01 Ist hier schon einmal ein Fehler in den Betrieb gelangt? Dann zuerst hierhin.
  2. 02 Wird die Datei oft geändert? Häufige Änderungen brauchen ein Netz.
  3. 03 Steckt eine Regel darin, also eine Berechnung oder ein Zustandswechsel?
  4. 04 Hängt Geld daran, etwa Preis, Menge, Rabatt oder Versand?
  5. 05 Leicht zu testen, aber harmlos? Diese Stellen kommen zuletzt.
Was du mitnimmst

Was du danach ohne langes Abwägen entscheidest

Testbarkeit ist keine Eigenschaft eines Werkzeugs, sondern eine Eigenschaft des Codes. Der Einstieg besteht deshalb aus zwei Bewegungen: die richtigen Stellen auswählen und an genau diesen Stellen so viel Struktur schaffen, dass ein Test überhaupt möglich wird.

Am Fehler anfangen, nicht am Einfachen

Der letzte Fehler aus dem Betrieb liefert dir Eingabedaten, erwartetes Verhalten und eine Begründung in einem. Er ist außerdem der einzige Test, dessen Nutzen niemand infrage stellt.

Reine Funktionen herausschneiden

Eine Funktion, die aus Argumenten ein Ergebnis berechnet und sonst nichts anfasst, ist ohne Aufbau testbar. Meist genügt es, die Berechnung aus dem Ereignisrückruf herauszuziehen und ihr die Werte zu übergeben.

Abhängigkeiten als Parameter übergeben

Uhrzeit, Zufall, Netzwerkzugriff und Speicher gehören nicht in die Logik, sondern werden ihr von außen übergeben. Ein Standardwert am Parameter hält alle bestehenden Aufrufstellen unverändert und macht den Test trotzdem möglich.

An den Systemgrenzen ersetzen, nicht im Inneren

Ersetz HTTP, Uhr und Dateisystem, also das, was du nicht besitzt. Wer eigene Module durch Attrappen ersetzt, prüft am Ende nur noch die eigene Annahme über sie.

Randfälle statt Sonnenscheinpfad

Der Mittelwert findet selten einen Fehler. Getestet gehören die Grenze, der leere Fall, der doppelte Eintrag und der Fehlerpfad, denn dort stecken die Regeln, über die niemand nachgedacht hat.

Abdeckung als Landkarte lesen

Die Zahl sagt nur, welche Zeilen ausgeführt wurden, nicht, ob etwas geprüft wurde. Nützlich ist die Ansicht der nicht berührten Stellen, nicht der Prozentwert darüber.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt

Welche Stellen zuerst

Die drei brauchbarsten Auswahlkriterien lassen sich ohne Diskussion prüfen. Erstens die Fehlerhistorie: Wo schon einmal etwas schiefging, geht wieder etwas schief. Zweitens die Änderungshäufigkeit, denn was oft angefasst wird, bricht oft. Die Liste dazu liefert die Versionsverwaltung selbst, etwa mit git log über das letzte Jahr, nur die Dateinamen ausgegeben, gezählt und absteigend sortiert. Drittens die fachliche Dichte: Preise, Rabatte, Fristen, Berechtigungen und Zustandswechsel.

Was auf dieser Liste selten weit oben steht, sind die Hilfsfunktionen: leicht zu testen, kaum in Bewegung und arm an Regeln. Ein paar Tests dort schaden nicht, nur sollten sie nicht der Inhalt der ersten zwei Wochen sein, weil sie den Eindruck erwecken, es gehe voran, während die riskanten Stellen unberührt bleiben.

Die zweite Achse ist die Teststufe. Die meisten Regeln lassen sich als Unit-Test prüfen, wenn sie erst einmal freigelegt sind. Für das Zusammenspiel mehrerer Teile ist ein Integrationstest ohne Browser oft das bessere Verhältnis von Aufwand zu Aussage. Und für die zwei oder drei Wege, an denen das Geschäft hängt, lohnt ein Test durch die echte Oberfläche, mehr davon werden schnell langsam und wackelig.

Warum sich alter Code nicht testen lässt

Die Gründe wiederholen sich. Module führen beim Import bereits etwas aus, bauen eine Verbindung auf oder lesen Konfiguration. Objekte werden mitten in der Logik mit new erzeugt, statt hineingegeben zu werden. Die Zeit kommt aus einem neuen Date-Objekt in der Berechnung selbst. Zugriffe gehen direkt auf document oder auf ein globales fetch. Jeder dieser Punkte macht aus einer Funktion etwas, das nur in einer vollständigen Anwendung läuft.

Die Gegenbewegung heißt Naht: eine Stelle, an der sich Verhalten austauschen lässt, ohne den Code zu ändern. Die billigste Naht ist ein zusätzlicher Parameter mit Standardwert, dicht gefolgt vom Herausziehen einer reinen Funktion. Beides lässt sich in Minuten machen und ändert an den bestehenden Aufrufstellen nichts. Deutlich teurer sind Nähte, die eine Klassenstruktur umbauen, und die brauchst du am Anfang nicht.

Wenn du nicht sicher weißt, was der Code tun soll, dreh die Reihenfolge um: Schreib einen Test, der festhält, was er heute tut, auch wenn das seltsam aussieht. Michael Feathers nennt das in Working Effectively with Legacy Code einen Characterization Test, und dort steht auch die nüchterne Definition, die diesen Einstieg begründet: Alter Code ist Code ohne Tests. Für eine anstehende Ablösung ist eine solche Sammlung das genaueste vorhandene Lastenheft.

Was Abdeckung sagt und was nicht

Abdeckung misst, welche Zeilen und Zweige während der Testläufe ausgeführt wurden. Ob dabei etwas geprüft wurde, misst sie nicht. Ein Test ohne eine einzige Erwartung hebt die Zahl genauso wie ein sorgfältig geschriebener. Deshalb ist ein Zielwert als Vorgabe eine der zuverlässigsten Methoden, wertlose Tests zu erzeugen: Sobald die Zahl das Ziel ist, hört sie auf, ein Messwert zu sein.

Als Werkzeug ist der Bericht trotzdem nützlich, aber in der anderen Richtung. Interessant sind nicht die 68 Prozent oben, sondern die rot markierten Zweige in einer Datei, die Regeln enthält. Ein nie ausgeführter Fehlerpfad in der Zahlungsabwicklung ist eine belastbare Aussage, der Prozentwert über die ganze Anwendung ist keine.

Wer wissen will, ob die vorhandenen Tests tatsächlich etwas fangen, kommt mit Mutationstests weiter. Dabei werden gezielt kleine Änderungen in den Code eingebaut, ein Vergleich umgedreht, eine Bedingung entfernt, und geprüft, ob ein Test daraufhin rot wird. Bleibt alles grün, ist die Zeile zwar abgedeckt, aber ungeprüft. Für JavaScript gibt es dafür Stryker.

Attrappen, die den Test wertlos machen

Die Grundregel lautet: Ersetz das, was du nicht besitzt. HTTP-Aufrufe, Uhrzeit, Zufall, Dateisystem und fremde Dienste sind gute Kandidaten. Eigene Module durch Attrappen zu ersetzen führt dagegen dazu, dass der Test nur noch prüft, ob deine Annahme über das eigene Modul zu deiner Annahme passt. Bricht das Modul später, bleibt der Test grün.

Bei HTTP ist die Ersetzung auf Protokollebene die stabilere Variante. Werkzeuge wie MSW fangen die Anfrage ab und antworten, ohne dass dein Code weiß, dass er im Test läuft. Der Vorteil zeigt sich beim Umbau: Ob intern fetch oder eine Bibliothek verwendet wird, ändert am Test nichts mehr, weil er auf der Ebene sitzt, die tatsächlich verabredet ist.

Der zweite häufige Fehler ist zu genaues Prüfen. Ein Test, der die Reihenfolge interner Aufrufe festschreibt, bricht bei jedem Umbau, ohne dass ein Fehler vorliegt. Prüf lieber das beobachtbare Ergebnis: den Rückgabewert, den geschriebenen Zustand, die abgeschickte Anfrage. Bei Oberflächen heißt das, über Rolle und Beschriftung zu suchen statt über Klassennamen, denn Rolle und Beschriftung sind das, was auch die Nutzer sehen.

Woran du einen brauchbaren Test erkennst

Er hat genau einen Grund, rot zu werden. Sein Name nennt die Regel und nicht die Funktion: gibt bei leerem Warenkorb null zurück sagt beim Fehlschlag bereits, was kaputt ist, während berechneSumme funktioniert nichts sagt. Er läuft unabhängig von seinen Nachbarn, teilt also keinen veränderlichen Zustand, und er läuft in beliebiger Reihenfolge, was sich mit gemischter Reihenfolge im Testlauf leicht überprüfen lässt.

Der Aufbau folgt drei Abschnitten: Ausgangslage herstellen, Aktion ausführen, Ergebnis prüfen. Wenn der erste Abschnitt dreißig Zeilen braucht, ist das eine Aussage über den Code und nicht über den Test. Genau an dieser Stelle lohnt sich der Umbau, denn ein aufwendiger Aufbau bedeutet, dass die Funktion zu viel gleichzeitig weiß.

Und schließlich muss ein Test ohne Netz und ohne besondere Umgebung laufen. Ein Test, der auf dem eigenen Rechner grün ist und in der Pipeline gelegentlich rot, wird nach dem dritten Mal übersprungen. Die häufigsten Ursachen dafür sind echte Zeit, echte Zufallszahlen, echte Netzwerkzugriffe und ein Aufräumen, das zwischen den Tests fehlt.

Dazu passende Kurse

Wie sich das ohne großen Umbau angehen lässt, üben die JavaScript-Kurse bei cmt am Beispiel, dort geht es unter anderem um die ersten Tests in einem gewachsenen Projekt .

Wer Teststufen, Testarten und Abnahmekriterien für ein ganzes Team ordnen muss, findet dafür Testverfahren und ihre Grenzen im Überblick .

Wissen prüfen

Wie sicher bist du beim Thema wirklich?

Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.

Ein sehr gutes, praxisorientiertes und nachhaltiges Seminar. So sollte es immer sein. Vielen Dank.
LimeSurvey - Anwendertraining Teil 1 (Grundlagen)
Die Schulung ist gerade für Einsteiger oder Entwickler*innen mit eingestaubtem Basiswissen sehr hilfreich.
Vue.js 3 Grundkurs
Ich bin sehr glücklich Franz als Trainer gehabt zu haben. Er hat offensichtlich unglaublich viel Wissen zu den Themen, und schafft es, dieses auch interaktiv und verständlich weiterzugeben.
React Komplettausbildung

Häufige Fragen

Unit-Tests oder Tests durch die Oberfläche, was zuerst?
Beides in kleiner Menge. Zwei oder drei Wege durch die echte Oberfläche fangen die Fälle ab, in denen alle Teile für sich funktionieren und das Ganze trotzdem nicht. Die Regeln selbst gehören in schnelle Tests ohne Browser, denn nur die kannst du bei jeder Änderung laufen lassen.
Wie viel Abdeckung ist genug?
Dafür gibt es keine allgemein gültige Zahl, und eine Vorgabe erzeugt vor allem Tests ohne Erwartungen. Brauchbarer ist die Frage nach den Stellen: Sind die Regeln abgedeckt, an denen Geld, Fristen oder Berechtigungen hängen, und sind deren Fehlerpfade mindestens einmal ausgeführt worden?
Lohnen sich Tests für ein Projekt, das ohnehin abgelöst wird?
Gerade dann. Tests, die das heutige Verhalten festhalten, sind während der Ablösung die einzige belastbare Beschreibung dessen, was die neue Lösung können muss. Ohne sie wird die Ablösung zu einer Sammlung von Nachfragen bei Leuten, die sich auch nur erinnern.
Was mache ich mit Code, den ich nicht ändern darf?
Schreib Tests, die sein aktuelles Verhalten festhalten, ohne ihn anzufassen. Notfalls über die Grenze, an der er benutzt wird, also über die Schnittstelle oder die Oberfläche. Damit hast du ein Netz für den Tag, an dem du ihn doch anfassen musst.
Persönlich für dich da

Deine Ansprechpartner

Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.

Yves Hoppe

Yves Hoppe

Weiterbildung & Beratung

Hilft dir, aus dem Webentwicklung-Programm den passenden Kurs für deinen Stand zu finden.

Norbert Jansen

Norbert Jansen

Beratung & Inhouse

Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.

Tests, die eine Änderung tatsächlich absichern

In den Test-Kursen bei cmt arbeitest du an einer gewachsenen Anwendung, schneidest Nähte in bestehenden Code und schreibst die Tests, die eine Änderung wirklich absichern.