Systematisch testen
Bisher hast du Tests benutzt – jetzt schreibst du sie selbst und lernst, sie systematisch auszuwählen. „Ich habe es ausprobiert und es lief“ ist kein Test.
Was ein Test leisten muss
Ein Test besteht immer aus drei Teilen:
- Vorbereiten: einen Ausgangszustand herstellen.
- Ausführen: die zu prüfende Methode aufrufen.
- Vergleichen: das Ergebnis mit dem vorher festgelegten Sollwert vergleichen.
Der dritte Punkt ist der entscheidende. Wer erst das Programm laufen lässt und dann sagt „ja, sieht plausibel aus“, testet nicht – er bestätigt nur, was das Programm ohnehin tut.
@Test
void testMaximum() {
Rechner r = new Rechner(); // vorbereiten
int erg = r.maximum(new int[]{3, 9, 1}); // ausführen
assertEquals(9, erg, "Das Maximum von 3, 9 und 1 ist 9."); // vergleichen
}
| Anweisung | prüft |
|---|---|
assertEquals(soll, ist, meldung) |
dass beide Werte gleich sind |
assertTrue(bedingung, meldung) |
dass die Bedingung wahr ist |
assertFalse(bedingung, meldung) |
dass sie falsch ist |
fail(meldung) |
schlägt immer fehl – für Stellen, die nie erreicht werden dürfen |
Die Meldung ist kein Beiwerk. Sie erscheint, wenn der Test fehlschlägt, und soll dann erklären, was erwartet wurde. „Fehler“ hilft niemandem, „Das Maximum von 3, 9 und 1 muss 9 sein“ schon.
Welche Testfälle braucht man?
Man kann nicht alles testen. Die Kunst besteht darin, wenige Fälle so zu wählen, dass sie viel abdecken.
Vier Sorten von Testfällen gehören zu jeder Methode:
| Sorte | Frage | Beispiel bei einer Suche im Feld |
|---|---|---|
| Normalfall | Was ist der typische Einsatz? | Wert steht irgendwo in der Mitte |
| Randfall | Was passiert an den Grenzen? | Wert steht ganz vorne oder ganz hinten |
| Sonderfall | Was ist die kleinstmögliche Eingabe? | leeres Feld, Feld mit einem Element |
| Fehlerfall | Was passiert bei unzulässiger Eingabe? | Wert kommt gar nicht vor, Index negativ |
Die meisten Fehler in Programmen stecken in den Rand- und Sonderfällen. Der Normalfall funktioniert fast immer – den hat man ja beim Schreiben vor Augen.
Testfälle finden
Gegeben ist die Beschreibung einer Methode:
int notenschnitt(int[] pPunkte)liefert den auf ganze Punkte gerundeten Durchschnitt. Punktzahlen außerhalb von 0 bis 15 werden ignoriert. Enthält das Feld keine gültige Punktzahl, wird −1 geliefert.
Finde ohne den Quelltext zu kennen mindestens acht Testfälle. Ordne sie den vier Sorten zu.
Auflösung
| Sorte | Eingabe | erwartet | prüft |
|---|---|---|---|
| Normalfall | {9, 10, 11} |
10 | die Grundrechnung |
| Normalfall | {9, 10} |
10 (9,5 gerundet) | die Rundung nach oben |
| Randfall | {0} |
0 | die untere Grenze ist gültig |
| Randfall | {15} |
15 | die obere Grenze ist gültig |
| Randfall | {16} |
−1 | knapp über der Grenze wird ignoriert |
| Randfall | {-1} |
−1 | knapp unter der Grenze ebenso |
| Sonderfall | {} |
−1 | leeres Feld |
| Sonderfall | {7} |
7 | ein einziger Wert |
| Fehlerfall | {99, 8, -5} |
8 | ungültige Werte werden übersprungen |
| Fehlerfall | {99, -5} |
−1 | nur ungültige Werte |
Beachte die Randfälle 15 und 16: Sie liegen direkt nebeneinander, aber auf verschiedenen Seiten der Grenze. Genau dort stecken die Zaunpfahlfehler – etwa wenn jemand pPunkte[i] < 15 statt <= 15 schreibt.
Man nennt das Grenzwertanalyse: An jeder Bereichsgrenze testet man den letzten gültigen und den ersten ungültigen Wert.
Aufgabe 1: Fehler durch Tests finden
Die folgende Klasse enthält drei Fehler. Alle Testfälle sind bereits geschrieben.
a) Führe die Tests aus. Welche schlagen fehl?
b) Sage anhand der Meldungen voraus, wo die Fehler stecken – ohne den Quelltext genau zu lesen.
c) Repariere sie und prüfe, dass alle Tests grün werden.
Tipp: Die Meldungen lesen
Schlägt der Test „0 Punkte sind eine gültige Punktzahl“ fehl, weißt du sofort: Die Bereichsprüfung schließt die untere Grenze fälschlich aus. Die Meldung führt dich direkt zur Zeile.
Deshalb lohnt sich der Aufwand, gute Meldungen zu schreiben.
Auflösung: die drei Fehler
pPunkte[i] > 0schließt die 0 aus. Richtig ist>= 0. Ein klassischer Zaunpfahlfehler an der unteren Grenze.pPunkte[i] < 15schließt die 15 aus. Richtig ist<= 15. Derselbe Fehler an der oberen Grenze.return summe / anzahl;teilt bei leerem Feld durch null. Der Sonderfallanzahl == 0fehlt – dort müsste −1 zurückgegeben werden.
Der dritte Fehler ist der schlimmste: Er lässt das Programm abstürzen, während die ersten beiden nur falsche Werte liefern. Falsche Werte fallen manchmal jahrelang nicht auf.
Bei zaehleMindestens steckt derselbe Fehlertyp noch einmal: > pGrenze statt >= pGrenze. Der Test deckt ihn auf, weil er ausdrücklich die Grenze selbst mitprüft.
Aufgabe 2: Testfälle selbst entwerfen
Zu dieser Beschreibung ist die Implementierung bereits fertig. Die Tests fehlen.
boolean istGueltigesDatum(int pTag, int pMonat, int pJahr)prüft, ob die drei Zahlen ein gültiges Datum ergeben. Berücksichtigt werden Monate mit 30 und 31 Tagen sowie Schaltjahre.
a) Entwerfe auf Papier mindestens zwölf Testfälle nach den vier Sorten. Notiere zu jedem, was er prüfen soll.
b) Schreibe sie als Testmethoden.
c) Die Implementierung enthält genau einen Fehler. Finde ihn mit deinen Tests.
Tipp: Wo lohnt sich das Suchen?
Die Schaltjahrregel hat drei Teile. Prüfe alle vier interessanten Jahre: 2024 (durch 4), 2023 (nicht durch 4), 1900 (durch 100) und 2000 (durch 400).
Zusatzaufgabe
Nimm dir eine der Datenstrukturen aus dem nächsten Kapitel vor – Stapel, Schlange oder Liste.
a) Entwirf vor dem Programmieren eine vollständige Testklasse. Denk an: leere Struktur, ein Element, mehrere Elemente, Entfernen aus einer leeren Struktur, Zugriff auf ein nicht vorhandenes Element.
b) Implementiere die Datenstruktur erst danach.
c) Beurteile: Hat das Schreiben der Tests vorher deine Implementierung verändert? Diese Arbeitsweise heißt testgetriebene Entwicklung und ist in der Praxis weit verbreitet.