Das Entscheidende beim Programmieren ist die Fähigkeit, hinlänglich bekannte Elemente in einem ganz neuen Kontext zu
verwenden. Solche Elemente gehören in der Regel zu bestimmten Klassen, z. B. Datenstrukturen (wie verlinkte Listen,
Hash-Tabellen oder relationale Datenbanken) oder Operationen (wie das Suchen, Sortieren, Erstellen temporärer Dateien oder
Anzeigen eines Browserfensters). Die relationalen Datenbanken zweier Kunden werden beispielsweise viele stereotype
Merkmale haben.
Das interessante an diesen Stereotypen ist, dass die mit ihnen verbundenen Fehlern ebenfalls stereotyp sind. Niemand erfindet eine vollkommen neue
Möglichkeit, etwas Falsches in eine doppelt verlinkte Liste einzufügen. Es besteht vielmehr die Tendenz, immer wieder
dieselben Fehler zu machen. Einem Programmierer, der ein Browserfenster anzeigen möchte, könnte einer dieser typischen
Fehler unterlaufen:
-
Er erstellt ein neues Fenster, obwohl das bereits geöffnete Fenster wiederverwendet werden soll.
-
Er unterlässt es, ein verdecktes oder auf Symbolgröße verkleinertes Fenster sichtbar zu machen.
-
Er verwendet Internet Explorer, obwohl der Benutzer einen anderen Standardbrowser gewählt hat.
-
Er überprüft nicht, ob JavaScript aktiviert ist.
Wenn wir es mit stereotypen Fehlern zu tun haben, können diese auch mit stereotypen Testideen gefunden werden. Nehmen Sie diese Testideen in Ihren Katalog mit
Testideen auf, damit Sie sie wiederverwenden können.
Einer der Vorzüge eines Katalogs ist, dass eine Testidee für die Erkennung mehrerer zugrunde liegender Fehler geeignet
sein kann. Das folgende Beispiel zeigt eine Idee, mit der zwei Fehler festgestellt werden.
Der erste Fehler ist in einem C-Compiler enthalten. Dieser Compiler wird mit Befehlszeilenoptionen wie "-table" oder
"-trace" oder "-nolink" verwendet. Die Optionen können abgekürzt werden, sofern ihre Eindeutigkeit gewahrt bleibt. Die
Option "-ta" hat beispielsweise denselben Effekt wie "-table". Die Angabe von "-t" ist jedoch unzulässig, da dies
"-table" oder "-trace" bedeuten könnte.
Intern sind die Befehlszeilenoptionen in einer Tabelle wie der folgenden gespeichert:
Eine in der Befehlszeile angegebene Option wird in der Tabelle gesucht. Die Präfixe der einzelnen Tabelleneinträge
werden als Treffer interpretiert. Die Angabe "-t" ist beispielsweise ein Treffer, weil sie mit dem ersten Buchstaben
von "-table" übereinstimmt. Wenn ein Treffer gefunden wurde, wird der Rest der Tabelle nach weiteren Treffern
durchsucht. Jeder weitere Treffer würde Mehrdeutigkeit anzeigen und wäre somit ein Fehler.
Der Code für die Suche sieht wie folgt aus:
for (first=0; first < size; first++)
{
if (matches(entry[first], thing_sought))
{ /* mindestens ein Treffer */
for(dup=first+1; dup < size; dup++) /* nach weiteren Treffern suchen */
if (matches(entry[dup], thing_sought)) /* zusätzliche Übereinstimmung */
break; /* Fehler ausgeben */
return first;
}
}
return -1; /* Nicht gefunden oder Mehrdeutigkeit */
Wie Sie sehen, ist das Problem nicht so einfach zu erkennen.
Der Fehler ist die Unterbrechungsanweisung. Sie soll die äußerste Programmschleife aufgliedern, wenn zwei Treffer
gefunden werden, tatsächlich gliedert sie jedoch die innere Schleife auf. Das hat denselben Effekt wie das Nichtfinden
eines zweiten Treffers: der Index des ersten Treffers wird zurückgegeben.
Dieser Fehler kann nur gefunden werden, wenn es in der Tabelle für die gesuchte Option zwei Treffer gibt, wie es bei
"-t" der Fall ist.
Schauen wir uns nun einen zweiten, ganz anders gearteten Fehler an.
Der Code soll in einer Zeichenfolge das letzte '=' durch '+' ersetzen. Falls die Zeichenfolge kein '=' enthält,
geschieht nichts. Der Code verwendet die C-Standardbibliotheksroutine strchr. Hier ist der Code:
ptr = strchr(string, '='); /* letztes = suchen */ if (ptr != NULL_CHAR) *ptr = '+';
Auch hier ist das Problem nicht ganz einfach zu finden.
Die Funktion strchr gibt das erste Vorkommen des Zeichens in der Zeichenfolge zurück und
nicht das letzte. Die korrekte Funktion lautet strrchr. Höchstwahrscheinlich wurde
das Problem durch einen Schreibfehler verursacht. (Eigentlich ist es generell unklug und problematisch, zwei Funktionen
in eine Standardbibliothek zu stellen, deren Namen sich nur durch einen Buchstaben unterscheiden, da ein Schreifehler
immer vorkommen kann.)
Dieser Fehler kann nur gefunden werden, wenn die Eingabe mehr als ein Gleichheitszeichen enthält. Das bedeutet
Folgendes:
-
"a=b" würde das richtige Ergebnis ("a+b") zurückgeben,
-
Enthält die Eingabe keine Leerzeichen, würde das richtige Ergebnis zurückgegeben werden ("noequals").
-
"a=b=c" würde fälschlicherweise zur Rückgabe von "a+b=c" führen, obwohl "a=b+c" eigentlich richtig wäre.
Interessant und hilfreich ist, dass wir hier zwei Fehler mit zwei ganz verschiedenen Grundursachen haben
(Schreibfehler, falsch verstandenes C-Konstrukt), die sich im Code unterschiedlich manifestieren (Aufruf der falschen
Funktion, unsachgemäße Verwendung einer Unterbrechungsanweisung) und doch mit nur einer Testidee (Suche nach
doppelten Vorkommen) gefunden werden können.
Was macht einen guten Katalog aus?
-
Er enthält nur wenige Testideen, mit denen sich sehr viele zugrunde liegende Fehler finden lassen.
-
Er ist leicht lesbar (kann überflogen werden). Sie sollten Testideen, die für Ihre Situation nicht relevant sind,
mühelos erkennen und übergehen können.
-
Er enthält nur Testideen, die Sie verwenden. Jemand, der nie mit Web-Browsern zu tun hat, sollte sich nicht durch
Testideen für Programme, die Web-Browser verwenden, kämpfen müssen. Jemand, der an einer Spielsoftware arbeitet,
wird einen kürzeren Katalog haben als jemand, der eine sicherheitsrelevanten Software arbeitet. Der Entwickler der
Spielsoftware kann sich ggf. ganz auf die Testideen konzentrieren, mit denen sich die meisten Fehler finden lassen.
Angesichts dieser Regeln scheint es am besten zu sein, mehrere Kataloge anzulegen. Einige Daten und Operationen werden
von allen Programmierern verwendet. Die entsprechenden Testideen können somit in einen Katalog aufgenommen werden, der
allen Programmierern zur Verfügung steht. Andere Daten und Operationen kommen nur in einem bestimmten Fachbereich vor.
Die zugehörigen Testideen können also in einen fachspezifischen Katalog aufgenommen werden.
Der nachfolgend verwendete Beispielkatalog (Adobe Reader
herunterladen) ist für den Anfang gut geeignet. Im Abschnitt Testideen für Ausdrücke mit kombiniertem logischem UND und logischem ODER finden Sie
ein weiteres Beispiel.
Hier sehen Sie, wie Sie den Beispielkatalog verwenden können. Nehmen wir an, Sie möchten die folgende Methode
implementieren:
void applyToCommonFiles(Verzeichnis V1, Verzeichnis V2, Operation Op);
applyToCommonFiles verwendet als Argumente zwei Verzeichnisse. Wenn eine Datei im ersten
Verzeichnis denselben Namen wie eine Datei im zweiten Verzeichnis hat, führt applyToCommonFiles
für diese beiden Dateien eine Operation aus. Die Unterverzeichnisse werden mit durchsucht.
Achten Sie beim Durchgehen des Katalogs auf Überschriften, die zu Ihrer Situation passen. Schauen Sie sich dann die
Testideen unter diesen Überschriften an, um festzustellen, ob sie relevant sind. Schreiben Sie anschließend die
relevanten Testideen in eine Liste mit
Testideen.
Anmerkung: Diese schrittweise Beschreibung mag die Verwendung des Katalogs aufwendig erscheinen lassen. Das
Lesen der Anleitung für das Erstellen der Prüfliste dauert jedoch länger als das eigentliche Erstellen.
Verwenden Sie also im Falle von applyToCommonFiles den Katalog wie hier beschrieben.
Der erste Eintrag lautet Any Object (Beliebiges Objekt). Gibt es Argumente, die Nullzeiger sein könnten? Dies
ist von den Bedingungen für applyToCommonFiles abhängig. Eine Bedingung könnte lauten, dass kein
Aufrufender einen Nullzeiger übergeben darf. Sollte dennoch ein Nullzeiger übergeben werden, kann nicht mehr mit dem
erwarteten Verhalten gerechnet werden. applyToCommonFiles könnte irgendeine Aktion ausführen. In
einem solchen Fall gibt es keinen geeigneten Test, da nichts, was applyToCommonFiles ausführt,
falsch sein kann. Wenn applyToCommonFiles jedoch zwingend eine Überprüfung auf Nullzeiger
durchführen muss, wäre die Testidee sinnvoll. Lassen Sie uns vom letztgenannten Fall ausgehen, der uns eine erste Liste
mit Testideen wie die folgende liefert:
-
V1 ist null (Fehlerfall).
-
V2 ist null (Fehlerfall).
-
Op ist null (Fehlerfall).
Der nächste Katalogeintrag lautet Strings (Zeichenfolgen). Die Namen der Dateien sind Zeichenfolgen, und es wird
überprüft, ob sie übereinstimmen. Die Idee, einen Test mit der leeren Zeichenfolge ("") durchzuführen, scheint nicht
sinnvoll zu sein. Wahrscheinlich werden Standardroutinen für den Vergleich von Zeichenfolgen verwendet, die leere
Zeichenfolgen richtig verarbeiten können.
Aber wie sieht es mit der Groß-/Kleinschreibung beim Vergleich von Zeichenfolgen aus? Nehmen wir an, V1 enthält eine Datei mit dem Namen "Datei" und V2 eine Datei mit dem Namen
"datei". Sollten diese Namen als übereinstimmend angesehen werden? Unter UNIX ist dies eindeutig nicht der Fall. Unter
Microsoft® Windows® werden sie mit größter Wahrscheinlichkeit als übereinstimmend interpretiert. So haben wir eine
weitere Testidee:
-
Die Namen der Dateien in beiden Verzeichnissen stimmen überein, unterscheiden sich jedoch hinsichtlich der
Groß-/Kleinschreibung.
Beachten Sie, dass diese Testidee nicht direkt aus dem Katalog stammt. Der Katalog hat aber unsere Aufmerksamkeit auf
einen bestimmten Aspekt des Programms gelenkt (Dateinamen als Zeichenfolgen), was uns auf eine zusätzliche Testidee
bringt. Es ist wichtig, den Katalog nicht buchstabengetreu anzuwenden. Sie sollten ihn vielmehr als Grundlage für einen
Gedankenaustausch und als Inspirationsquelle für neue Ideen nutzen.
Der nächste Eintrag lautet Collections (Datensammlungen). Ein Verzeichnis ist eine Sammlung von Dateien. Viele
Programme zur Verarbeitung von Datensammlungen scheitern, wenn die Sammlung leer ist. Einige der Programme, die leere
Datensammlungen oder Sammlungen mit vielen Elementen verarbeiten können, scheitern bei Sammlungen mit genau einem
Element. Daher sind folgende Ideen nützlich:
-
V1 ist leer.
-
V2 ist leer.
-
V1 enthält genau eine Datei.
-
V2 enthält genau eine Datei.
Die nächste Idee gilt der Verwendung einer Datensammlung mit maximaler Größe. applyToCommonFiles
wird normalerweise für kleine Verzeichnisse verwendet. Käme ein Benutzer auf die Idee, diese Methode auf zwei
umfangreiche Verzeichnisstrukturen mit tausenden Dateien anzuwenden, würde er dabei lediglich feststellen, dass absurd
viel Speicher benötigt wird und die Methode für diesen realistischen Fall nicht in Frage kommt.
Die absolute Maximalgröße eines Verzeichnisses zu testen, ist nicht wichtig. Es muss nur so groß sein, wie ein Benutzer
es benötigen könnte. Es sollte dennoch mindestens einen Test mit mehr als drei Dateien in einem Verzeichnis
geben.
-
V1 enthält sehr viele Dateien.
-
V2 enthält sehr viele Dateien.
Die letzte Testidee (doppelte Vorkommen, duplicate elements) ist auf ein Verzeichnis mit Dateien nicht anwendbar. Wenn
es ein Verzeichnis gibt, das zwei Dateien mit demselben Namen enthält, liegt unabhängig von applyToCommonFiles ein Problem vor. Das Dateisystem ist beschädigt.
Der nächste Katalogeintrag lautet Searching (Suche). Diese Testideen können für applyToCommonFiles wie folgt umgesetzt werden:
-
V1 und V2 enthalten keine gemeinsamen Dateien (alle Dateien haben unterschiedliche Namen).
-
V1 und V2 enthalten genau eine gemeinsame Datei (die bei alphabetischer Sortierung das letzte Element im
Verzeichnis ist).
-
V1 und V2 enthalten mehrere gemeinsame Dateien.
Mit der letzten Testidee wird applyToCommonFiles überprüft. Kehrt die Methode zurück, sobald sie
die erste Übereinstimmung gefunden hat? Voraussetzung für die Anmerkung in Klammern (bei der zweiten Testidee) ist,
dass das Programm die Liste der Dateien in einem Verzeichnis mit einer Bibliotheksroutine abruft, die diese Liste
alphabetisch sortiert zurückgibt. Ist dies nicht der Fall, wäre es besser, die letzte Datei als übereinstimmende Datei
zu verwenden. Bevor Sie sich zeitaufwendig darüber informieren, wie Dateien sortiert werden, sollten Sie sich fragen,
wie wahrscheinlich es ist, dass Mängel leichter festgestellt werden können, weil sich ein übereinstimmendes Element an
letzter Stelle befindet. Ein Element an das Ende einer Datensammlung zu stellen, könnte hilfreich sein, wenn der Code
die Datensammlung explizit mit einem Index durchgeht. Wird ein Iterator verwendet, ist es äußerst unwahrscheinlich,
dass die Reihenfolge eine Rolle spielt.
Schauen wir uns noch einen Eintrag im Beispielkatalog an. Der Eintrag Linked structures (verlinkte Strukturen)
erinnert uns daran, Verzeichnisstrukturen zu vergleichen und nicht nur unstrukturierte Sammlungen von Dateien. Wenn wir
nun entscheiden wollen, wie applyToCommonFiles getestet werden soll, werden wir mit der
Unvollständigkeit der Beschreibung dieser Methode konfrontiert.
Nehmen wir an, die Verzeichnisstruktur sieht wie folgt aus:
Abbildung 1: Verzeichnisstruktur
Wird applyToCommonFiles auch das Unterverzeichnis Cdir durchsuchen? Dies
scheint keinen Sinn zu machen. Dieses Unterverzeichnis kann keine Übereinstimmung mit irgendeinem Dateinamen in der
anderen Verzeichnisstruktur enthalten. Es sieht so aus, als ob Dateien in Unterverzeichnissen nur übereinstimmen
können, wenn auch die Namen der Unterverzeichnisse übereinstimmen. Gehen wir nun von dieser Verzeichnisstruktur aus:
Abbildung 2: Zweite Verzeichnisstruktur
Die Dateien mit dem Namen "Datei" stimmen nicht überein, weil sie sich in verschiedenen Unterverzeichnissen befinden.
Die Unterverzeichnisse sollten nur durchsucht werden, wenn sie in beiden Strukturen, V1 und
V2, dieselben Namen haben. Dies führt uns zu folgenden Testideen:
-
Ein Unterverzeichnis von V1 ist in V2 nicht vorhanden (wird nicht durchsucht).
-
Ein Unterverzeichnis von V2 ist in V1 nicht vorhanden (wird nicht durchsucht).
-
Ein Unterverzeichnis erscheint in V1 und V2 (wird durchsucht).
Daraus ergeben sich jedoch weitere Fragen. Sollte die Operation (Op) auf übereinstimmende
Verzeichnisse oder nur auf die übereinstimmenden Dateien angewendet werden? Wenn sie auch auf Unterverzeichnisse
angewendet wird, sollte dies dann vor oder nach dem Durchsuchen der Unterverzeichnisse geschehen? Das macht
beispielsweise einen Unterschied, wenn die Operation das übereinstimmende Verzeichnis bzw. die übereinstimmende Datei
löscht. Sollte die Operation somit die Verzeichnisstruktur modifizieren dürfen? Und wenn ja, was wäre dann das
korrekte Verhalten von applyToCommonFiles? (Dies ist dieselbe Problemstellung, die sich auch bei
Iteratoren ergibt.)
Solche Fragen kommen normalerweise auf, wenn Sie die Beschreibung zur Erstellung von Testideen für eine Methode
gründlich durchlesen. Im Moment wollen wir diese Fragen jedoch unberücksichtigt lassen. Wie die Antworten auch immer
aussehen mögen, es müssen Testideen daraus entwickelt werden, mit denen überprüft werden kann, ob der Code die
Antworten richtig implementiert.
Schauen wir uns jetzt wieder den Katalog an, denn wir haben uns noch nicht mit all seinen Testideen beschäftigt. Die
erste Idee (leere Verzeichnisstruktur, empty) erfordert, dass das Verzeichnis leer ist. Einen solchen Fall hatten wir
bereits beim Eintrag Collections (Datensammlungen). Den Fall einer nicht leeren Struktur mit minimalem
Inhalt (minimal non-empty) hatten wir ebenfalls (ein Verzeichnis mit nur einem Element). Solche Redundanzen sind
nicht unüblich, können aber einfach ignoriert werden.
Wie sieht es mit einer kreisförmigen Struktur (circular) aus? Verzeichnisstrukturen können nicht kreisförmig
sein. Ein Verzeichnis kann nicht in einem seiner Unterverzeichnisse oder sich selbst enthalten sein, oder doch? Und wie
verhält es sich mit Verknüpfungen (unter Windows) oder symbolischen Links (unter UNIX)? Falls es in der
Verzeichnisstruktur von d1 eine Verknüpfung gibt, die zurück auf V1
zeigt, soll applyToCommonFiles die Struktur dann endlos durchsuchen? Die Antwort auf diese
Fragen könnte zu einigen neuen Testideen führen:
-
V1 ist kreisförmig, weil es Verknüpfungen oder symbolische Links gibt.
-
V2 ist kreisförmig, weil es Verknüpfungen oder symbolische Links gibt.
In Abhängigkeit vom korrekten Verhalten kann es weitere Testideen geben.
Schließlich stellt sich noch die Frage nach einer Verschachtelungstiefe größer als eins (depth greater than
one). Mit den bisherigen Testideen können wir sicherstellen, dass die erste Unterverzeichnisebene mit durchsucht wird.
Wir sollten jedoch wie folgt überprüfen, ob applyToCommonFiles auch weitere Verzeichnisebenen
einbezieht:
-
Es werden mehrere Unterverzeichnisebenen (>1) von V1 berücksichtigt.
-
Es werden mehrere Unterverzeichnisebenen (>1) von V2 berücksichtigt.
Wie bereits erwähnt, kann der generische Katalog nicht alle von Ihnen benötigten Testideen enthalten. Die fachspezifischen Kataloge werden jedoch nicht außerhalb der
erstellenden Unternehmen publiziert. Falls Sie Bedarf an solchen Katalogen haben, müssen Sie sie selbst erstellen. Dazu
finden Sie nachfolgend einige Ratschläge.
-
Füllen Sie einen Katalog nicht mit Spekulationen darüber, welche Ideen für die Fehlersuche geeignet sein könnten.
Denken Sie daran, dass jede in den Katalog aufgenommene Idee Zeit und Geld kostet.
-
Sie brauchen Zeit, den Katalog zu verwalten.
-
Andere Programmierer brauchen Zeit, über die Testidee nachzudenken.
-
Andere Programmierer werden möglicherweise Zeit für die Implementierung eines entsprechenden Tests
aufwenden.
Fügen Sie nur Ideen hinzu, die nachweislich zum Erfolg geführt haben. Sie sollte auf mindestens einen
tatsächlichen Fehler verweisen können, der mit dieser Testidee gefunden werden kann. Im Idealfall wäre das ein
Fehler, der erst vor Ort festgestellt und somit bei anderen Tests übersehen wurde. Eine gute Grundlage für die
Erstellung von Katalogen ist die Fehlerdatenbank Ihres Unternehmens. Gehen Sie diese Datenbank durch und fragen
Sie, wie die einzelnen Fehler rechtzeitig hätten entdeckt werden können.
-
-
Einen Katalog mit Testideen werden Sie nicht in den Pausen oder in Ihrer Freizeit erstellen und verwalten können.
Wie für jede andere wichtige Aufgabe benötigen Sie speziell dafür vorgesehene Zeit. Sie sollten Ihren Katalog mit
Testideen im Rahmen der Aktivität Test-Assets verbessern erstellen und verwalten.
|