Informatik

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:

  1. Vorbereiten: einen Ausgangszustand herstellen.
  2. Ausführen: die zu prüfende Methode aufrufen.
  3. 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
  1. pPunkte[i] > 0 schließt die 0 aus. Richtig ist >= 0. Ein klassischer Zaunpfahlfehler an der unteren Grenze.
  2. pPunkte[i] < 15 schließt die 15 aus. Richtig ist <= 15. Derselbe Fehler an der oberen Grenze.
  3. return summe / anzahl; teilt bei leerem Feld durch null. Der Sonderfall anzahl == 0 fehlt – 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.

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

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).

Lösung. Erfrage das Passwort bei deiner Lehrkraft.

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.


Selbsttest

Systematisch testen

Teilbare URL erstellen

Abschnitte auswählen