Einführung
Der Begriff "Entwicklertest" wird verwendet, um die Testaufgaben zu kategorisieren, die am besten von
Softwareentwicklern durchgeführt werden können. Er umfasst auch die Arbeitsergebnisse, die von diesen Aufgaben erstellt
werden. Entwicklertests umfassen die Arbeiten, die traditionell einer der folgenden Kategorien zugeordnet sind:
Einheitentests, ein Großteil der Integrationstests und verschiedene Aspekte dessen, was häufig als Systemtest
bezeichnet wird. Während Entwicklertests traditionell Aufgaben der Disziplin Implementierung zugeordnet werden, haben
sie auch Beziehungen zu Aufgaben der Disziplin Analyse und Design.
Der ganzheitliche Ansatz des Entwicklertests hilft, die Risiken, die mit dem traditionell verwendeten spezifischen
Ansatz verbunden sind, zu mindern. Beim traditionellen Ansatz des Entwicklertests konzentriert man sich zunächst
darauf, zu prüfen, ob alle Einheiten unabhängig voneinander funktionieren. In der späten Phase des Entwicklungszyklus,
wenn die Entwicklungsarbeit fast abgeschlossen ist, werden die integrierten Einheiten in einem funktionierenden
Subsystem oder System assembliert und zum ersten Mal mit diesen Einstellungen getestet.
Dieser Ansatz hat eine Reihe von Schwächen. Erstens, weil er beim Testen der integrierten Einheiten und später beim
Testen der Subsysteme abschnittweise vorgeht, werden die Fehler, die bei diesen Tests identifiziert werden, oft zu spät
erkannt. Das führt normalerweise dazu, dass keine Korrekturmaßnahmen ergriffen werden oder die Korrektur sehr viel
Nacharbeit erfordert. Diese Nacharbeit ist aufwändig und verlangsamt den Fortschritt in anderen Bereichen. Damit erhöht
sich das Risiko, dass das Projekt sich in die falsche Richtung entwickelt oder sogar aufgegeben werden muss.
Wenn Sie starre Grenzen zwischen Einheit, Integration und Systemtest schaffen, erhöht das die Wahrscheinlichkeit, dass
grenzübergreifende Fehler von niemandem erkannt werden. Das Risiko erhöht sich proportional, wenn die Zuständigkeit für
diese Testtypen eigenständigen Teams zugeordnet wird.
Die Darstellung, die von RUP für Entwicklungstests empfohlen wird, hält den Entwickler an, sich auf die Tests zu
konzentrieren, die zu einem bestimmten Zeitpunkt am nützlichsten und geeignetsten sind. Selbst wenn man nur den Bereich
einer einzelnen Iteration betrachtet, ist es für den Entwickler normalerweise sinnvoller, möglichst viele Mängel in
seinem eigenen Code zu finden, ohne mit dem zusätzlichen Aufwand, der für die Kommunikation mit einer anderen
Testgruppe erforderlich ist, belastet zu sein. Wünschenswert ist, die wichtigsten Softwarefehler früh zu ermitteln,
unabhängig davon, ob diese Fehler bei der unabhängigen Einheit, der Integration der Einheiten oder der Ausführung der
integrierten Einheiten innerhalb eines sinnvollen Endbenutzerszenarios auftreten.
Risiken beim Beginn des Entwicklertests
Viele Entwickler, die versuchen, sehr gründliche Tests zu erstellen, geben kurz danach auf, da sie den Eindruck
gewinnen, dass diese Vorgehensweise keinen Nutzen bringt. Außerdem erkennen einige Entwickler, dass sie ihre anfänglich
effektiven Entwicklertests inzwischen zu einer nicht mehr handhabbaren Testsuite weiterentwickelt haben, die irgendwann
ohnehin aufgegeben wird.
Auf dieser Seite finden Sie Richtlinien, die Ihnen helfen, über die ersten Hürden zu gelangen, und eine Testsuite zu
erstellen, mit der Sie diese Falle umgehen können. Weitere Informationen finden Sie im Abschnitt Richtlinie: Automatisierte Testsuites verwalten.
Erwartungen definieren
Diejenigen Entwickler, die Entwicklertests lohnend finden, führen sie durch, diejenigen, die sie als lästige
Routinearbeit ansehen, vermeiden sie möglichst. So ist schlicht die Situation bei den meisten Entwicklern in fast allen
Branchen. Diesen Umstand als beschämenden Mangel an Disziplin zu geißeln, bringt erfahrungsgemäß keinen Erfolg. Daher
sollten Sie als Entwickler erwarten, dass Tests nützlich sind, und alles Nötige dazu tun.
Idealerweise folgen Entwicklertests einem sehr kurzen Kreislauf aus Bearbeitung und Test. Sie nehmen eine kleine
Änderung am Produkt vor, z. B. das Hinzufügen einer neuen Methode zu einer Klasse, dann führen Sie die Tests sofort
erneut aus. Wenn ein Test fehlschlägt, wissen Sie genau, welches Codesegment fehlerhaft ist. Dieser einfache,
beständige Entwicklungsrhythmus ist der größte Vorzug der Entwicklertests. Lange Debug-Sitzungen sollten die Ausnahme
sein.
Da es nicht ungewöhnlich ist, dass eine Änderung in einer Klasse etwas in einer anderen Klasse beschädigt, sollten Sie
erwarten, nicht nur die Tests für die geänderten Klassen, sondern viele Tests erneut durchführen zu müssen.
Idealerweise führen Sie die vollständige Testsuite für Ihre Komponente viele Male pro Stunde durch. Jedes Mal, wenn Sie
eine wichtige Änderung vornehmen, müssen Sie die Suite ausführen, die Ergebnisse prüfen und mit der nächsten Änderung
fortfahren bzw. die letzte Änderung korrigieren. Gehen Sie davon aus, dass Sie etwas Zeit brauchen, um diesen schnellen
Feedback zu ermöglichen.
Tests automatisieren
Eine häufige Testausführung ist bei manuellen Tests nicht praktikabel. Bei einigen Komponenten sind automatische Tests
einfach durchzuführen. Ein Beispiel dafür ist eine Speicherdatenbank. Sie kommuniziert mit ihren Clients über eine API
und hat keine andere Schnittstelle zur Außenwelt. Ein passender Test können wie folgt aussehen:
/* Prüfen, ob Elemente höchstens einmal hinzugefügt werden können.
*/
// Konfiguration
Database db = new Database();
db.add("key1", "value1");
// Test
boolean result = db.add("key1", "another value");
expect(result == false);
Die Tests unterscheiden sich in einer Hinsicht vom normalen Clientcode: Die Ergebnisse von API-Aufrufen werden nicht
einfach übernommen, sondern geprüft. Wenn es einfach ist, über die API Clientcode zu schreiben, ist es auch einfach,
Testcode zu schreiben. Wenn der Testcode nicht einfach zu schreiben ist, erhalten Sie eine Frühwarnung, die
besagt, dass die API verbessert werden kann. Das Test-First-Design ist daher mit dem Ansatz von Rational Unified
Process, wichtige Risiken früh zu behandeln, konsistent.
Je enger die Komponente mit der Außenwelt verbunden ist, desto einfacher ist sie zu testen. Es gibt zwei allgemeine
Fälle: grafische Benutzerschnittstellen und Back-End-Komponenten.
Grafische Benutzerschnittstellen
Nehmen Sie an, dass die Datenbank im obigen Beispiel ihre Daten über einen Callback von einem
Benutzerschnittstellenobjekt empfängt. Der Callback wird gestartet, wenn der Benutzer einige Textfelder ausfüllt und
eine Taste betätigt. Diesen Vorgang zu Testzwecken viele Male pro Stunde auszuführen, ist zu umständlich. Sie müssen
einen Weg finden, die Eingabe programmgesteuert vorzunehmen und quasi das Betätigen der Taste durch Code zu
realisieren.
Das Betätigen der Taste bewirkt, dass Code in der Komponente ausgeführt wird. Höchstwahrscheinlich ändert dieser Code
den Status einiger Benutzerschnittstellenobjekte. Also müssen Sie auch einen Weg finden, diese Objekte
programmgesteuert abzufragen.
Back-End-Komponenten
Nehmen Sie an, dass die zu testende Komponente keine Datenbank implementiert, sondern stattdessen ein Wrapper für eine
reale Datenbank auf Platte darstellt. Das Testen mit dieser realen Datenbank kann schwierig sein. Die Installation und
Konfiguration der Datenbank kann schwierig sein. Lizenzen für die Datenbank können teuer sein. Die Datenbank kann die
Tests so sehr verlangsamen, dass Sie geneigt sein könnten, die Tests nicht oft durchzuführen. In diesen Fällen lohnt es
sich, die Datenbank durch eine simplere Komponente zu ersetzen, die lediglich die Tests unterstützt.
Stubs sind auch nützlich, wenn eine Komponente, mit der Ihre Komponente kommuniziert, noch nicht fertig ist. Sie
möchten sicherlich vermeiden, dass der Test, den Sie durchführen möchten, davon abhängt, dass ein anderer Entwickler
seinen Code fertigstellt.
Weitere Informationen finden Sie im Abschnitt Konzept: Stubs.
Keine eigenen Tools schreiben
Entwicklertests scheinen recht klar zu sein. Sie konfigurieren einige Objekte, setzen einen Aufruf über eine API ab und
prüfen das Ergebnis. Wenn die Ergebnisse nicht Ihren Erwartungen entsprechen, kündigen Sie einen Testfehler an. Es
empfiehlt sich auch, Tests in irgendeiner Weise zusammenzufassen, damit sie einzeln oder als vollständige Testsuites
ausgeführt werden können. Tools, die diese Anforderungen unterstützen, werden als Testgerüste bezeichnet.
Entwicklertests sind klar und die Anforderungen für Testgerüste nicht kompliziert. Wenn Sie allerdings Ihr
eigenes Testgerüst schreiben, werden Sie viel mehr Zeit damit verbringen, an diesem Gerüst herumzubessern, als Sie
vielleicht ahnen. Es sind viele handelsübliche und Open-Source-Testgerüste verfügbar, und es gibt keinen Grund, nicht
eines davon zu verwenden.
Unterstützungscode erstellen
Testcode ist häufig eintönig und enthält Codesequenzen wie die folgende:
// Nullname nicht zulässig
retval = o.createName("");
expect(retval == null);
// führende Leerzeichen nicht zulässig
retval = o.createName(" l");
expect(retval == null);
// nachgestellte Leerzeichen nicht zulässig
retval = o.createName("name ");
expect(retval == null);
// erstes Zeichen darf nicht numerisch sein
retval = o.createName("5allpha");
expect(retval == null);
Dieser Code wird folgendermaßen erstellt: Man kopiert eine Prüfung, fügt sie ein und bearbeitet sie, um eine neue
Prüfung durchzuführen.
Bei dieser Vorgehensweise haben Sie ein doppeltes Risiko. Wenn die Schnittstelle sich ändert, ist viel
Bearbeitungsaufwand erforderlich. (In komplizierteren Fällen ist eine einfache Ersetzung nicht ausreichend.) Außerdem
kann die Testabsicht bei kompliziertem Code im Text verloren gehen.
Wenn Sie feststellen, dass Sie sich wiederholen, sollten Sie ernsthaft darüber nachdenken, die Wiederholung in
Unterstützungscode auszulagern. Obwohl der oben genannte Code ein einfaches Beispiel ist, ist er in folgender Form
einfacher zu lesen und zu verwalten:
void expectNameRejected(MyClass o, String s) {
Object retval = o.createName(s);
expect(retval == null); }
...
// Nullname nicht zulässig
expectNameRejected(o, "");
// führende Leerzeichen nicht zulässig
expectNameRejected(o, " l");
// nachgestellte Leerzeichen nicht zulässig
expectNameRejected(o, "name ");
// erstes Zeichen darf nicht numerisch sein
expectNameRejected(o, "5alpha");
Entwickler, die Tests schreiben, machen häufig den Fehler, zu viele Teile zu kopieren und einzufügen. Wenn Sie den
Eindruck haben, dass das auch bei Ihnen der Fall sein könnte, tun Sie ganz bewusst das Gegenteil. Beschließen Sie, alle
Textduplikate aus dem Code zu entfernen.
Tests zuerst schreiben
Tests nach dem Code zu schreiben, ist eine lästige Routinearbeit. Man verspürt den Wunsch, die Aufgabe schnell
abzuschließen und weiterzuarbeiten. Wenn Tests vor dem Code geschrieben werden, werden sie Teil einer positiven
Feedback-Schleife. Während Sie weiteren Code implementieren, sehen Sie, dass immer mehr und schließlich alle Tests
abgeschlossen werden können. Dann sind Sie mit Ihrer Arbeit fertig. Entwickler, die Tests zuerst schreiben, scheinen
mehr Erfolg zu haben, und der Zeitaufwand ist derselbe. Weitere Informationen zur Priorisierung von Tests finden Sie im
Abschnitt Konzept: Test-First-Design.
Tests verständlich gestalten
Sie sollten bedenken, dass Sie oder jemand anderes die Tests zu einem späteren Zeitpunkt ändern muss. Eine typische
Situation ist, dass sich bei einer späteren Iteration die Notwendigkeit ergibt, das Verhalten der Komponente zu ändern.
Nehmen Sie z. B. an, dass die Komponente einmal eine Quadratwurzelmethode wie die folgende deklariert hat:
double sqrt(double x);
In dieser Version führt ein negatives Argument dazu, dass sqrt NaN ("not a number", siehe IEEE-Standard
754-1985, Standard for Binary Floating-Point Arithmetic) zurückgibt. In der neuen Iteration akzeptiert die
Quadratwurzelmethode negative Zahlen und gibt ein komplexes Ergebnis zurück:
Complex sqrt(double x);
Alte Tests für sqrt müssen geändert werden. Dazu müssen Sie verstehen, wie sie funktionieren, und Sie müssen
sie aktualisieren, damit sie mit der neuen Quadratwurzelmethode sqrt funktionieren. Beim Aktualisieren von
Tests müssen Sie darauf achten, ihr Fehlerermittlungspotenzial nicht zu beeinträchtigen. Sie können z. B. wie folgt
vorgehen:
void testSQRT () {
// Tests für komplexe Zahlen aktualisieren
// Bei Gelegenheit -- bem
/* double result = sqrt(0.0); ... */ }
Die folgende Situation erfordert eine komplexere Vorgehensweise: Die Tests wurden geändert und werden ausgeführt, aber
sie testen nicht mehr das, was sie ursprünglich testen sollten. Das Endergebnis nach vielen Iterationen kann eine
Testsuite sein, die zu schwach ist, um viele Programmfehler ermitteln zu können. Dieser Vorgang wird auch als Verfall
einer Testsuite bezeichnet. Eine solche Suite wird letztendlich aufgegeben, da es sich nicht lohnt, sie zu pflegen.
Sie können das Fehlerermittlungspotenzial eines Tests nur aufrechterhalten, wenn klar ist, welche Testideen ein Test implementiert. Im Gegensatz zu Produktcode wird Testcode wird
häufig zu wenig kommentiert, obwohl der Grund für sein Vorhandensein oft schwerer zu verstehen ist.
Der Verfall der Testsuite ist in direkten Tests für sqrt weniger wahrscheinlich als in den indirekten Tests. Es gibt
Code, der sqrt aufruft. Für diesen Code gibt es Tests. Wenn sqrt geändert wird, werden einige dieser Tests
fehlschlagen. Der Entwickler, der sqrt ändert, muss diese Tests wahrscheinlich ebenfalls ändern. Da er diese
Tests jedoch weniger kennt und deren Beziehung zur Änderung weniger klar ist, ist es wahrscheinlicher, dass er die
Tests, während er mit seiner Arbeit versucht, sie zu einem erfolgreichen Abschluss zu bringen, in ihrer Aussagekraft
schwächt.
Wenn Sie Unterstützungscode für Tests erstellen (wie oben erläutert), müssen Sie mit Sorgfalt vorgehen: Der
Unterstützungscode muss den Zweck der Tests, die diesen Code verwenden, klarstellen und darf ihn nicht verschleiern.
Ein allgemeiner Einwand gegen objektorientierte Programme ist, dass es keinen echten Ausgangspunkt gibt. Wenn Sie
irgendeine Methode in Augenschein nehmen, sehen Sie nur, dass der Methodeninhalt an eine andere Stelle weitergeleitet
wird. Eine solche Struktur hat Vorteile, erschwert es jedoch neuen Mitarbeitern, den Code zu verstehen. Es kann leicht
geschehen, dass sie Änderungen vornehmen, die falsch sind oder den Code komplexer und fragiler machen. Dasselbe trifft
auf Testcode zu, und hier ist die Wahrscheinlichkeit, dass neue Mitarbeiter mit dem Code nicht adäquat umgehen, sogar
noch höher. Sie können dieses Problem dadurch vermeiden, dass Sie verständliche Tests schreiben.
Teststruktur mit der Produktstruktur abgleichen
Nehmen Sie an, andere Entwickler haben Ihre Komponente übernommen. Sie müssen einen Abschnitt ändern und möchten daher
die alten Tests untersuchen, als Hilfestellung für das neue Design. Sie möchten die alten Tests aktualisieren, bevor
sie den Code schreiben (Test-First-Design).
Alle diese guten Ansätze sind nutzlos, wenn sie die passenden Tests nicht finden können. Es bleibt ihnen dann nur, die
Änderungen vorzunehmen, zu prüfen, welche Tests fehlschlagen und diese Tests dann zu prüfen. Das trägt zum Verfall der
Testsuite bei.
Aus diesem Grund ist es wichtig, dass die Testsuite gut strukturiert ist und die Position der Tests sich aus der
Produktstruktur ergibt. Normalerweise ordnen Entwickler Tests in einer parallelen Hierarchie an, mit einer Testklasse
pro Produktklasse. Wenn also jemand eine Klasse mit dem Namen Log ändert, weiß er, dass der Name der
Testklasse TestLog ist und wo die Quellendatei zu finden ist.
Anforderungen für Kapselung bewusst missachten
Sie können Ihre Tests darauf beschränken, dass Sie wie der Clientcode mit Ihrer Komponente interagieren, und zwar über
dieselbe Schnittstelle, die auch der Clientcode verwendet. Dieser Ansatz hat jedoch Nachteile. Nehmen Sie an, Sie
testen eine einfache Klasse, die eine doppelt verlinkte Liste verwaltet:
Abbildung 1: Doppelt verlinkte Liste
Sie testen insbesondere die Methode DoublyLinkedList.insertBefore(Object existing, Object newObject). In einem
der Tests können Sie ein Element in die Mitte der Liste einfügen und dann prüfen, ob es eingefügt wurde. Der Test
verwendet die obige Liste, um diese aktualisierte Liste zu erstellen:
Abbildung 2: Doppelt verlinkte Liste mit eingefügtem Element
Die Richtigkeit der Liste wird wie folgt untersucht:
// Die Liste hat jetzt einen Eintrag mehr.
expect(list.size()==3);
// das neue Element ist an der korrekten Position
expect(list.get(1)==m);
// Prüfen, ob alle anderen Elemente noch vorhanden sind
expect(list.get(0)==a); expect(list.get(2)==z);
Das scheint ausreichend zu sein, ist es aber nicht. Nehmen Sie an, dass die Implementierung der Liste falsch ist und
Rückwärtszeiger nicht richtig gesetzt sind. Mit anderen Worten, nehmen Sie an, dass die aktualisierte Liste wie folgt
aussieht:
Abbildung 3: Doppelt verlinkte Liste - Implementierungsfehler
Wenn DoublyLinkedList.get(int index) die Liste von Anfang bis Ende abarbeitet (was wahrscheinlich ist), würde
der Test diesen Fehler übersehen. Wenn die Klasse die Methoden elementBefore und elementAfter
bereitstellt, ist die Fehlersuche einfach:
// Prüfen, ob alle Links aktualisiert wurden
expect(list.elementAfter(a)==m);
expect(list.elementAfter(m)==z);
expect(list.elementBefore(z)==m);
//Dies scheitert.
expect(list.elementBefore(m)==a);
Aber was ist, wenn diese Methoden nicht bereitgestellt werden? Sie könnten ausgefeiltere Sequenzen von Methodenaufrufen
planen, die fehlschlagen, wenn der vermutete Fehler auftritt. Die folgende Methode würde funktionieren:
// Prüfen, ob Rück-Link von Z korrekt ist.
list.insertBefore(z, x);
// Wurde fälschlicherweise keine Aktualisierung durchgeführt, wird X
// direkt hinter A eingefügt.
expect(list.get(1)==m);
Allerdings ist solch ein Test arbeitsaufwändiger bei der Erstellung und wahrscheinlich wesentlich schwerer zu pflegen.
(Wenn Sie keine guten Kommentare schreiben, wird die Funktionsweise des Tests nicht klar sein.) Es gibt zwei Lösungen:
-
Die Elemente elementBefore und elementAfter zur öffentlichen Schnittstelle hinzufügen. Dadurch
wird die Implementierung jedoch für alle zugänglich, künftige Änderungen werden so erschwert.
-
Die Zeiger mit den Tests direkt prüfen.
Die letztere ist normalerweise die beste Lösung, selbst für eine einfache Klasse wie DoublyLinkedList und
insbesondere für die komplexeren Klassen, die in den Produkten auftreten.
Normalerweise werden Tests in dasselbe Paket gestellt wie die Klassen, die sie testen. Sie erhalten ein geschütztes
bzw. ein Friend-Zugriffsrecht.
Charakteristische Fehler im Testdesign
Jeder Test führt eine Komponente aus und prüft, ob die Ergebnisse korrekt sind. Das Testdesign, d. h. die Eingaben die
der Test verwendet, und die Art und Weise, in der er auf Richtigkeit prüft, kann gut geeignet sein, Mängel aufzudecken
oder diese unbeabsichtigt verdecken. Es folgen einige charakteristische Fehler im Testdesign.
Erwartete Fehler vorab nicht angeben
Nehmen Sie an, es wird eine Komponente getestet, die XML in HTML konvertiert. Hier könnte der Tester der Versuchung
erliegen, ein XML-Beispielsegment zu nehmen, zu konvertieren und dann die Ergebnisse in einem Browser zu untersuchen.
Wenn die Anzeige keine Fehler aufweist, könnte er das erzielte Ergebnis als offizielles erwartetes Ergebnis speichern
und HTML "absegnen". Danach würde der Test dann die tatsächliche Ausgabe der Konvertierung mit den erwarteten
Ergebnissen vergleichen.
Diese Vorgehensweise ist riskant. Selbst fortgeschrittene Computerbenutzer sind daran gewöhnt, dem Computer zu
vertrauen. Es ist wahrscheinlich, dass Sie Fehler in der Bildschirmanzeige übersehen. (Ganz zu schweigen davon, dass
die Browser bezüglich schlecht formatierter HTML sehr tolerant sind.) Wenn Sie die falsche HTML zu den offiziellen
erwarteten Ergebnissen machen, garantieren Sie, dass der Test das Problem nie ermitteln kann.
Es ist weniger riskant, die HTML-Segmente noch mal zu prüfen, es besteht jedoch immer noch ein Risiko. Da die Ausgabe
kompliziert ist, werden Fehler leicht übersehen. Sie können mehr Mängel ausfindig machen, wenn Sie die erwartete
Ausgabe zunächst manuell eingeben.
Hintergrund nicht prüfen
Tests prüfen normalerweise, dass die Elemente, die geändert werden sollten, auch tatsächlich geändert wurden. Die
Testersteller vergessen jedoch oft, zu prüfen, ob die Elemente, die nicht angerührt werden sollten, auch tatsächlich
nicht angerührt wurden. Nehmen Sie beispielsweise an, dass ein Programm die ersten 100 Sätze in einer Datei ändern
soll. In diesem Fall macht es Sinn, sicherzustellen, dass der 101. Satz nicht geändert wurde.
In der Theorie prüfen Sie, dass keine im Hintergrund ausgeführten Komponenten - das gesamte Dateisystem, der gesamte
Speicher, alle über das Netz erreichbaren Komponenten - verändert wurden. In der Praxis müssen Sie jedoch die
Komponenten, die Sie prüfen können, sorgfältig auswählen. Es ist sehr wichtig, diese Auswahl zu treffen.
Persistenz nicht prüfen
Nur weil die Komponente Ihnen mitteilt, dass eine Änderung durchgeführt wurde, heißt das nicht, dass diese Änderung
tatsächlich in der Datenbank festgeschrieben wurde. Sie müssen die Datenbank auf anderem Wege prüfen.
Keine Vielfältigkeit bereitstellen
Es kann ein Test entworfen werden, um die Wirkung von drei Feldern in einem Datenbanksatz zu überprüfen, aber es müssen
viele andere Felder ausgeführt werden, um den Test auszuführen. Tester neigen dazu, immer wieder dieselben Werte für
diese vermeintlich irrelevanten Felder zu verwenden. Beispielsweise verwenden sie den Namen ihrer Freundin oder ihres
Freundes in einem Textfeld oder die Zahl 999 in einem numerischen Feld.
Das Problem ist, dass scheinbar unwichtige Dinge manchmal sehr wohl wichtig sein können. Hin und wieder tritt ein
Programmfehler auf, der auf einer unbekannten Kombination unwahrscheinlicher Eingaben beruht. Wenn Sie immer dieselben
Werte verwenden, haben Sie keine Chance, diese Fehler zu ermitteln. Wenn Sie die Eingaben ständig variieren, sind Ihre
Chancen einfach größer. In den meisten Fällen ist der Aufwand, eine andere Zahl als 999 oder einen anderen Namen zu
verwenden, nicht erwähnenswert. Wenn das Ändern der Werte in Tests also leicht zu bewerkstelligen ist und einen
potenziellen Nutzen hat, tun Sie es.
Es gibt noch einen anderen Vorteil. Es kann z. B. durchaus passieren, dass das Programm das Feld X anstelle des
Feldes Y verwendet. Wenn beide Felder den Wert "Dawn" haben, kann der Fehler nicht ermittelt werden.
Unrealistische Daten verwenden
Es ist üblich, in Tests erfundene Daten zu verwenden. Diese Daten sind oft unrealistisch einfach. Beispielsweise können
Kundennamen Mickey, Snoopy und Donald sein. Da diese Daten sich von Eingaben realer Benutzer unterscheiden -
beispielsweise sind sie typischerweise kürzer - enthalten sie möglicherweise nicht die Mängel, die von realen Kunden
gesehen werden können. Beispielsweise kann bei Namen, die aus nur einem Wort bestehen, nicht erkannt werden, dass der
Code keine Namen mit Leerzeichen unterstützt.
Daher ist es klug, ein wenig zusätzliche Zeit in die Verwendung realistischer Daten zu investieren.
Übersehen, dass der Code keine Operation ausführt
Nehmen Sie an, Sie initialisieren einen Datenbanksatz mit null, führen eine Berechnung durch, die null ergibt und im
Satz gespeichert wird, und prüfen dann, ob der Satz null ist. Was hat dieser Test gezeigt? Möglicherweise wurde die
Berechnung überhaupt nicht durchgeführt. Möglicherweise wurde nichts gespeichert, und der Test konnte darüber keine
Aussage machen.
Dieses Beispiel klingt unwahrscheinlich. Aber dieser Fehler kann auf subtilere Weise in Erscheinung treten. Nehmen Sie
an, Sie schreiben einen Test für ein kompliziertes Installationsprogramm. Der Test soll sicherstellen, dass alle
temporären Dateien nach einer erfolgreichen Installation entfernt wurden. Aufgrund all der Optionen des
Installationsprogramms in diesem Test wurde jedoch eine bestimmte temporäre Datei nicht erstellt. Mit Sicherheit
handelt es sich um die Datei, die das Programm nicht entfernt hat.
Übersehen, dass der Code die falsche Operation ausführt
Manchmal führt ein Programm die richtige Operation aus dem falschen Grund aus. Ein einfaches Beispiel dafür ist der
folgende Code:
if (a < b && c)
return 2 * x;
else
return x * x;
Der logische Ausdruck ist falsch, und Sie haben einen Test geschrieben, der bewirkt, dass eine falsche Bewertung
vorgenommen und die falsche Abzweigung genommen wird. Leider hat die Variable X rein zufällig den Wert 2 im Test. Also
ist das Ergebnis der falschen Abzweigung zufällig richtig und entspricht genau dem Ergebnis, dass die richtige
Abzweigung ergeben hätte.
Sie sollten bei jedem erwarteten Ergebnis prüfen, ob es eine Möglichkeit gibt, dass das Ergebnis auf falschem Wege
entstanden sein könnte. Es ist zwar oft unmöglich, dies herauszufinden, manchmal aber hat man diese Möglichkeit.
|