Konzept: Liste mit Testideen
In einer Liste mit Testideen sind die Testideen in absteigender Folge nach Wichtigkeit sortiert und bestimmten Teststrategien für die Entwicklung ausführbarer Tests verknüpft.
Beziehungen
Hauptbeschreibung

Einführung

Beim Entwerfen von Tests werden viele Informationsquellen genutzt, z. B. Designmodelle, Klassifizierungsmerkmale, Schnittstellen, Zustandsdiagramme und der eigentliche Code. Diese Informationen aus den Quellendokumenten müssen zu gegebener Zeit in ausführbare Tests umgesetzt werden:

  • spezifische Eingaben für die zu testende Software
  • eine bestimmte Hardware- und Softwarekonfiguration
  • Initialisierung in einem bekannten Status
  • spezifische erwartete Ergebnisse

Es ist möglich, die Informationen aus einem Quellendokument direkt in ausführbare Tests umzusetzen. Oft ist es jedoch sinnvoll, einen Zwischenschritt einzuschieben, der das Schreiben von Testideen in eine Liste mit Testideen vorsieht. Anhand dieser Liste werden dann die ausführbaren Tests erstellt.

Was sind Testideen?

Eine Testidee wird manchmal auch als zu testende Anforderung bezeichnet und ist eine kurze Angabe zu einem Test, der ausgeführt werden könnte. Nehmen wird als einfaches Beispiel eine Funktion, die eine Quadratwurzel berechnet, und für die wir einige Testideen entwickeln wollen:

  • Als Eingabe soll eine Zahl verwendet werden, die geringfügig kleiner als null ist.
  • Als Eingabe soll null verwendet werden.
  • Es soll eine perfekte Quadratzahl wie 4 oder 16 getestet werden. (Ist das Ergebnis genau 2 oder 4?)

Jede dieser Ideen ließe sich sofort in einen ausführbaren Test mit genauen Beschreibungen der Eingaben und der erwarteten Ergebnisse umsetzen.

Diese weniger spezifische Zwischenform hat zwei Vorteile:

  • Testideen lassen sich besser prüfen und sind leichter verständlich als komplette Tests. Die Gedankengänge, die zu einem bestimmten Test geführt haben, lassen sich besser nachvollziehen.
  • Testideen unterstützen kombinierte und komplexere Tests. Dies ist weiter unten unter der Überschrift Testdesign mit Liste genauer beschrieben.

Alle unsere Beispiele zur Quadratwurzel beschreiben Eingaben. Testideen können jedoch alle Elemente eines ausführbaren Tests umfassen. Die Angaben "Auf einem Laserjet IIIP drucken" oder "Mit gefüllter Datenbank testen" beschreiben beispielsweise einen Aspekt der Testumgebung für einen Test. Sie sind jedoch ziemlich unvollständig, denn es wird nicht gesagt, was auf dem Drucker gedruckt oder was mit der gefüllten Datenbank getestet werden soll. Dennoch stellen diese Angaben sicher, dass wichtige Ideen, die später detaillierter im Testdesign beschrieben werden können, nicht vergessen werden.

Testideen basieren oft auf Fehlermodellen, dem Verständnis, welche Fehler in einer Software plausibel sind, und dem Wissen, wie solche Fehler mit Tests aufgedeckt werden können. Nehmen wir Grenzwerte als Beispiel. Es darf sicher angenommen werden, dass die Quadratwurzelfunktion wie folgt implementiert werden könnte:

double sqrt(double x) {
    if (x < 0)
      // Fehler signalisieren
    ...

Dass für < fälschlicherweise <= angegeben werden kann, erscheint ebenfalls plausibel. Diese Art Fehler kommt häufig vor und sollte daher auf jeden Fall überprüft werden. Wenn X den Wert 2 hat, kann der Fehler nicht gefunden werden, denn sowohl der falsche Ausdruck (x<=0) als auch der richtige (x<0) führen zu derselben Verzweigung der Anweisung if. Wenn wir für X den Wert -5 verwenden, kann der Fehler ebenfalls nicht gefunden werden. Die einzige Möglichkeit, den Fehler aufzudecken, ist die Verwendung des Wertes 0 für X, womit die zweite Testidee bestätigt wird.

Das Fehlermodell ist hier explizit, es kann in anderen Fällen aber auch implizit sein. Wenn ein Programm beispielsweise einen verlinkte Struktur manipuliert, sollte getestet werden, ob die neue Struktur kreisförmig ist. Es gibt viele Fehler, die unbeabsichtigt zu einer kreisförmigen Struktur führen und nicht einzeln aufgezählt werden müssen. Es reicht aus, dass ein Fehler wahrscheinlich genug ist, um einen Test zu rechtfertigen.

Wenn Sie die folgenden Links anklicken, erfahren Sie, wie Testideen aus verschiedenen Arten von Fehlermodellen entwickelt werden können. Die beiden ersten Abschnitte beschäftigen sich mit expliziten Fehlermodellen und der letzte Abschnitt mit impliziten.

Diese Fehlermodelle lassen sich auf viele verschiedene Arbeitsergebnisse anwenden. Der erste Abschnitt beschreibt beispielsweise die Möglichkeiten, die Sie bei Booleschen Ausdrücken haben. Solche Ausdrücke sind in Code, Wächterbedingungen, Zustands- und Folgediagrammen enthalten, aber auch in normalen Beschreibungen des Verhaltens von Methoden (wie Sie sie für eine publizierte API finden können).

Gelegentlich ist es hilfreich, Richtlinien für bestimmte Arbeitsergebnisse zu haben. Lesen Sie hierzu den Abschnitt Richtlinie: Testideen für Zustandsdiagramme und Ablaufdiagramme.

Eine Liste mit Testideen kann Ideen aus vielen Fehlermodellen enthalten, die wiederum aus mehreren Arbeitsergebnissen abgeleitet sein können.

Testdesign mit Liste

Nehmen wir an, Sie entwerfen Tests für eine Methode, die in einer sequenziellen Datensammlung nach einer Zeichenfolge sucht. Die Methode kann bei der Suche die Groß-/Kleinschreibung beachten oder ignorieren und gibt den Index der ersten gefundenen Übereinstimmung zurück. Wird keine Übereinstimmung gefunden, gibt die Methode -1 zurück.

int Collection.find(String string,
            Boolean ignoreCase);

Hier sehen Sie einige Testideen für diese Methode:

  1. Die Übereinstimmung wird an der ersten Position gefunden.
  2. Die Übereinstimmung wird an der letzten Position gefunden.
  3. Es wird keine Übereinstimmung gefunden.
  4. Es wurden zwei oder mehr Übereinstimmungen in der Datensammlung gefunden.
  5. Die Groß-/Kleinschreibung wird ignoriert. Es wird eine Übereinstimmung gefunden, die jedoch bei Berücksichtigung der Groß-/Kleinschreibung keine solche wäre.
  6. Die Groß-/Kleinschreibung wird beachtet. Es wird eine exakte Übereinstimmung gefunden.
  7. Die Groß-/Kleinschreibung wird beachtet. Eine Zeichenfolge, die bei ignorierter Groß-/Kleinschreibung eine Übereinstimmung gewesen wäre, wird übersprungen.

Es wäre einfach, für jede dieser Ideen einen Test zu implementieren, so dass Sie insgesamt sieben Tests ausführen müssten. Sie können jedoch verschiedene Testideen in einem Test zusammenfassen. Mit dem folgenden Test würden beispielsweise die Testideen 2, 6 und 7 umgesetzt werden:

Konfiguration: Datensammlung initialisiert mit ["dawn", "Dawn"]
Aufruf: collection.find("Dawn", false)
Erwartetes Ergebnis: Rückgabewert 1 (Der Wert läge bei 0, wenn "dawn" nicht übersprungen werden würde.)

Je weniger spezifisch Testideen sind, desto leichter lassen sie sich kombinieren.

Alle hier genannten Testideen ließen sich in drei Tests unterbringen. Und warum sind drei Tests für sieben Testideen besser als sieben Einzeltests?

  • Wenn Sie eine Vielzahl einfacher Tests entwerfen, wird für die Erstellung von Test N+1 in der Regel der Test N kopiert und gerade so weit modifiziert, dass mit ihm die neue Testidee realisiert werden kann. Insbesondere bei etwas komplexerer Software wird der Test N+1 das Programm wahrscheinlich fast genauso ausführen wie der Test N und fast genau demselben Pfad durch den Code folgen.

    Eine kleinere Anzahl von Tests, die jeweils mehrere Testideen enthalten, lässt ein solches Kopieren und Modifizieren nicht zu. Jeder Test läuft somit etwas anders als der vorherige ab, so dass der Code auf verschiedene Weise und entlang verschiedener Pfade ausgeführt wird.

    Und welchen Vorteil hat das? Wenn die Liste mit Testideen vollständig wäre und für jeden Programmfehler eine Testidee enthalten würde, machte es keinen Unterschied, wie Sie die Tests schreiben. In der Liste fehlen jedoch immer einige Testideen, die Programmfehler aufdecken könnten. Wenn scheinbar nicht notwendige Varianten hinzugefügt werden, damit sich die Schritte eines Tests deutlich von denen des vorherigen Tests unterscheiden, erhöht sich die Chance, dass Sie mit einem der Tests durch Zufall auf einen Programmfehler stoßen. Bei wenigen komplexen Tests besteht tatsächlich eine größere Wahrscheinlichkeit, dass Sie einer Testidee nachgehen, die Sie sonst gar nicht in Erwägung gezogen hätten.

  • Manchmal werden Ihnen beim Erstellen komplexerer Tests auch neue Ideen einfallen. Bei einfachen Tests geschieht dies weit seltener, weil so viele Schritte genau mit denen des vorherigen Tests übereinstimmen und damit routinemäßiges Denken und Handeln begünstigen.

Es gibt aber auch Gründe, die gegen komplexe Tests sprechen.

  • Wenn mit jedem Test genau eine Testidee umgesetzt wird und der Test für die Idee 2 scheitert, wissen Sie sofort, woran dies höchstwahrscheinlich liegen wird. Das Programm ist nicht in der Lage, eine Übereinstimmung an der letzten Position zu finden. Bei einem Test, der die Ideen 2, 6 und 7 umsetzt, lässt sich der Fehler nicht so leicht eingrenzen.

  • Komplexe Tests sind schwerer zu verstehen und zu verwalten, weil die Absicht des Tests nicht so offensichtlich ist.

  • Es ist auch schwieriger, komplexe Tests zu entwerfen. Einen Test zu konstruieren, mit dem fünf Testideen umgesetzt werden sollen, kostet oft mehr Zeit als das Konstruieren von fünf Tests für jeweils eine Idee. Darüber hinaus schleichen sich auch schneller Fehler ein. Sie könnten denken, dass Sie allen fünf Testideen Rechnung tragen, obwohl Sie eigentlich nur vier umsetzen.

In der Praxis müssen Sie Komplexität und Einfachheit vernünftig gegeneinander abwägen. Die ersten Tests, denen Sie die Software unterziehen (in der Regel die Smoke Tests), sollten einfach, leicht verständlich und leicht zu verwalten sein und das Ziel haben, die offensichtlichsten Probleme festzustellen. Spätere Tests sollten komplexer sein, jedoch nicht so komplex, dass sie nicht mehr verwaltet werden können.

Sobald Sie eine Gruppe von Tests fertig gestellt haben, sollten Sie sie auf die im Abschnitt Konzept: Entwicklertests erläuterten typischen Testdesignfehler überprüfen.

Testideen vor dem Testen verwenden

Eine Liste mit Testideen kann bei der Überprüfung von Designarbeitsergebnissen hilfreich sein. Schauen Sie sich beispielsweise diesen Teil eines Designmodells mit der Beziehung zwischen den Klassen 'Abteilung' und 'Mitarbeiter' an.

Abbildung 'Designmodellbeispiel'

Abbildung 1: Beziehung zwischen den Klassen 'Abteilung' und 'Mitarbeiter'

Nach den Regeln für die Erstellung von Testideen aus einem solchen Modell müssten Sie den Fall berücksichtigen, in dem eine Abteilung viele Mitarbeiter hat. Wenn Sie ein Design durchgehen und sich fragen, wie es an dieser Stelle wohl aussehen würde, wenn die Abteilung viele Mitarbeiter hat, könnten Sie Design- oder Analysefehler entdecken. Sie könnten beispielsweise erkennen, dass im Design immer nur jeweils ein Mitarbeiter von einer Abteilung in eine andere versetzt werden kann. Dies könnte ein Problem sein, wenn das Unternehmen häufiger umfassende Restrukturierungsmaßnahmen durchführt, bei denen viele Mitarbeiter die Abteilung wechseln.

Fehler, bei denen eine Möglichkeit übersehen wird, werden als Versäumnisfehler bezeichnet. Solche Fehler kommen aber nicht nur im Design vor. Sie können auch Tests übersehen haben, mit denen sich diese Fehler feststellen ließen. Schauen Sie sich beispielsweise [GLA81], [OST84], [BAS87], [MAR00] und andere Studien an, die belegen, wie häufig Versäumnisfehler erst beim Deployment festgestellt werden.

Die Rolle von Tests im Bereich der Designaktivitäten ist im Abschnitt Konzept: Test-First-Design näher erläutert.

Testideen und Rückverfolgbarkeit

Bei der Rückverfolgbarkeit muss immer ein geeigneter Kompromiss gefunden werden. Rechtfertigt der Nutzen der Rückverfolgbarkeit die damit verbundenen Kosten? Diese Frage muss beim Definieren der Anforderungen an die Auswertung und Rückverfolgbarkeit berücksichtigt werden (siehe  Aufgabe: Anforderungen für Bewertung und Rückverfolgbarkeit definieren).

Wenn die Rückverfolgbarkeit lohnt, werden in der Regel Tests auf die Arbeitsergebnisse zurückgeführt, aus denen sie hergeleitet wurden. Es könnte beispielsweise eine Rückverfolgbarkeit zwischen einer API und den Tests für diese API geben. Wenn sich die API ändert, wissen Sie, welche Tests geändert werden müssen. Ändert sich der Code (der die API implementiert), wissen Sie, welche Tests ausgeführt werden müssen. Falls Ihnen ein Test Kopfzerbrechen bereitet, können Sie herausfinden, welche API mit diesem Test überprüft werden soll.

Die Liste mit Testideen eröffnet zusätzliche Möglichkeiten für die Rückverfolgbarkeit. Sie können einen Test auf die darin umgesetzten Testideen zurückführen und diese Testideen auf das ursprüngliche Arbeitsergebnis.