Test
Ziel des Testens, ist es möglichst viele Fehler aufzudecken. Ein Fehler
ist jede Abweichung des Verhaltens von dem in der Anforderungsdefinition
festgelegten Verhalten. Testen ist eine projektbegleitende Maßnahme
zur Qualitätssicherung und bezieht sich auf die Phasen Systemspezifikation,
Entwurf, Implementierung und Wartung. Wenn dennoch von einer eigenen Testphase
im Software-Life-Cycle die Rede ist, dann deshalb, weil der Test des Endprodukts
erst nach Abschluss der vorhergegangenen Phasen möglich ist. Die im
folgenden beschriebenen Tätigkeiten verteilen sich aber auf alle Phasen
des Software-Life-Cycle, und finden nicht nur in der Testphase statt.
Eng verbunden mit dem Testen ist das Debugging, mit dem Fehlerursachen
gefunden und behoben werden sollen, während Testen nur darauf abzielt,
Fehler zu entdecken.
Testmethoden
Was wird mit welchem Ziel und mit welchen Methoden getestet?
-
Systemspezifikation
-
Vollständigkeit, Klarheit, Konsistenz und Realisierbarkeit ist im
Spezifikationstest zu prüfen. Eine wichtige Rolle dabei spielen Prototypen
der Benutzerschnittstelle und einzelner Systemkomponenten. Der Spezifikationstest
muss stets mit dem Benutzer gemeinsam ausgeführt werden.
-
Module
-
Abweichungen zwischen der Modulimplementierung und der Modulspezifikation
sind im Modultest aufzudecken. Module sind aber häufig nicht für
sich alleine lauffähig; sie können oft nur ausgeführt werden,
wenn andere Module vorliegen. Beim Modultest muss daher oft eine passenden
Modulumgebung
geschaffen werden, die das Verhalten der (noch) fehlenden Module simuliert.
Diese Testumgebung bereitzustellen, ist oft ein wesentlicher Teil der Arbeit
beim Modultest. Der Modultest erfolgt während der Implementierungsphase
und wird vom Modulimplementierer ausgeführt. Erst nach bestandenem
Modultest wird eine Modul zur Verwendung im Programmsystem freigegeben.
-
Modulverbindungen
-
Nach erfolgreichen Modultests zielt der Integrationstest darauf ab, Module
zu Subsystemen zusammenzufassen und dabei Fehler in der Kommunikation der
Module untereinander aufzudecken. Auch für Subsysteme müssen
Testumgebungen geschaffen werden.
-
Gesamtsystem
-
Sobald die Integration aller Subsysteme zum Gesamtsystem möglich ist,
kann die Suche nach Abweichungen des Systemverhaltens von dem in der Anforderungsdefinition
beschriebenen beginnen. Nicht nur Vollständigkeit der Benutzeranforderungen
und Korrektheit der Ergebnisse sind zu prüfen, auch Zuverlässigkeit
und Robustheit des Systems gegen fehlerhafte Benutzereingaben. Darüberhinaus
sind die nicht-funktionalen Anforderungen wie z.B. Effizienz, ..
zu prüfen. Umfang und Anzahl der Tests sowie die Wahl der richtigen
Testdaten sind vom jeweiligen Anwendungsfall abhängig.
-
Akzeptanz
-
Die Entwicklung eines SW-Produktes endet mit dem Abnahmetest durch den
Benutzer. Das SW-Produkt wird jetzt mit realen Daten unter realen Einsatzbedingungen
getestet. Erst dieser Test zeigt, ob die Anwendung sowohl mit der vereinbarten
Anforderungsdefinition als auch mit den Erwartungen des Benutzers übereinstimmt.
Bei den oben aufgeführten Tests können die folgenden Testmethoden
eingesetzt werden:
Verifikation von Algorithmen
Mit einem Test lassen sich Fehler entdecken. Tests können jedoch nicht
die Abwesenheit von Fehlern zeigen: Ein bestandener Test garantiert nicht,
dass die entworfene Komponente wirklich korrekt ist, d.h. dass sie keine
Fehler enthält. Zu diesem Zweck sind verschiedene, mathematisch orientierte
Verifikationstechniken vorgeschlagen worden. Sie zielen darauf ab, die
Korrektheit einer Komponente in voller mathematischer Strenge zu beweisen.
Diese Techniken sind für den Korrektheitsbeweis kurzer und einfacher
Algorithmen erfolgreich angewendet worden. Bei grossen Programmsystemen
steigen aber die Schwierigkeiten so stark an, so dass hier Verifikation
keine praktische Testhilfe darstellt.
Statische Programmanalyse
Hierbei gilt es, durch syntaktische, strukturelle oder semantische Analyse
Fehler frühzeitig zu finden, ohne das Testobjekt direkt auszuführen.
Die wichtigsten Tätigkeiten sind:
-
Code-Inspektion
-
Der Autor bespricht mit anderen Softwaretechnikern sein Programm Schritt
für Schritt. Fagan schlägt dazu ein Team von vier Personen vor:
einen erfahrenen, am Projekt nicht beteiligten SW-Techniker als Moderator,
den Designer des Testobjektes, den für die Implementierung zuständigen
Programmierer und den für den Test verantwortlichen Mitarbeiter. Entdeckte
Fehler werden vom Moderator notiert und klassifiziert. Zum Ende der Inspektion
werden Designer und Programmierer mit der Korrektur beauftragt. Während
der Inspektion werden nur Fehler aufgedeckt, nicht deren Korrektur diskutiert.
-
Komplexitätsanalyse
-
Maßzahlen für die Komplexität eines Programmms werden ermittelt,
meist von eigens dafür geschaffenen Werkzeugen. Zu diesen Masszahlen
gehören z.B. Komplexitätsmaße von Modulen (nach McCabe
oder Halstead), Schachtelungstiefen von Schleifen, Längen von Prozeduren,
Import- und Verwendungszahlen von Modulen, ...Damit soll man schneller
fehlerträchtige Stellen im Programm einkreisen können, nach der
Faustregel, dass komplexe Programmteile eher Fehler enthalten als Teile
mit geringerer Komplexitätsmaßzahl. Die Aussagekraft dieser
Maßzahlen ist allerdings umstritten, Komplexität ist schwer
objektiv zu messen.
-
Strukturanalyse
-
Strukturanomalien wie z.B. unerreichbarer Programmtext ("toter Code"),
Schleifen mit Einsprüngen, unerlaubte Kontrollstrukturen ("goto")
werden (von Werkzeugen) aufgedeckt. Oft sind sie ein Indiz für tieferliegende
Fehler.
-
Datenflussanalyse
-
Datenflussanomalien wie z.B. Verwendung eines Datenobjektes vor seiner
Initialisierung oder Nichtverwendung eines Datenobjektes nach einer Zuweisung
liefern oft auch Hinweise für tieferliegende Fehler.
Dynamisches Testen
Hierbei werden die Testobjekte ausgeführt oder simuliert. Die wichtigsten
Tätigkeiten sind dabei:
-
Testobjekte vorbereiten (instrumentieren),
-
Testumgebung bereitstellen,
-
Testfälle und -daten auswählen,
-
Test ausführen und auswerten.
Black-Box und White-Box-Test
Prinzipiell kann jedes Testobjekt auf zwei Arten geprüft werden:
-
als Black-Box
-
ohne Kenntnis der inneren Struktur (Schnittstellentest). Abweichungen des
Testobjektes von seiner Schnittstellenbeschreibung sollen aufgedeckt werden.
Nur die Information in der Schnittstellenbeschreibung kann zur Konstruktion
von Testfällen verwendet werden. Mit diesem Ansatz ist es nicht möglich
hrauszufinden, ob alle Funktionen des Testobjektes wirklich benötigt
werden oder ob Datenobjekte manipuliert werden, die keinen Einfluss auf
das Verhalten des Objektes haben.
-
als White-Box
-
mit Berücksichtigung der inneren Struktuer (Schnittstellen- und Strukturtest).
Bei der Wahl der Testfälle wird u.a. berücksichtigt, dass
-
jede Funktion mindestens einmal aufgerufen wird,
-
jeder Zweig der Ablaufstruktur mindestens einmal durchlaufen wird,
-
möglichst viele Pfade durchlaufen werden.
-
Alle Pfade zu testen ist selbst bei automatischer Testdatengenerierung
praktisch unmöglich, weil dann zuviele Testläufe benötigt
werden.
Testplanung und -ausführung
Die Testobjekte der unteren Schichten (Module bzw. Klassen) eines SW-Systems
sollen von den Implementierern selbst, die Integration von Testobjekten
zu Subsystemen sollte jedoch von Mitarbeitern getestet werden, die
nicht an der Implementierung beteiligt waren.
Testplanung
Möglichst früh, am besten schon in der Phase der Systemspezifikation
sollen folgende Punkte zumindest grob festgelegt werden:
-
Testmethode,
-
Art und Umfang der Testdokumentation,
-
die Testobjekte und an sie gestellte Qualitätsanforderungen,
-
die zu erfüllenden Testkriterien (Anzahl der Testfälle, Prozentsatz
der zu testenden Programmpfade, ...),
-
Art des Abnahmetests für die einzelnen Testobjekte,
-
die Testpersonen,
-
die einzusetzenden Testwerkzeuge
Testobjekte vorbereiten
Erkennt man einen Fehler, so ist er im nächsten Schritt zu lokalisieren.
Am einfachsten lokalisiert man erkannte Fehler, indem man den Programmablauf
verfolgt (tracing, breakpoints setzen) oder indem Zustände des Testobjektes
verfolgt (Zustandsgrößen ausgeben) oder überwacht (z.B.
Schwellwertüberwachung, Nichteinhaltung benutzerdefinierter Bedingungen,
assertions) werden. Gängige Programmierumgebungen (Debugger, Inspector,
..) erleichtern diese Aufgaben sehr, trotzdem wird man immer noch die Testobjekte
vorbereiten müssen, z.B. durch ein- und auschaltbare Anweisungen.
Testfälle und Testdaten auswählen
Eine Testfallbeschreibung gibt an, welches Testobjekt und welche seiner
Funktionen geprüft wird, mit welchen Eingabedaten (= Testdaten) welche
Ergebnisse erwartet werden.
Vollständiges Testen ist in der Regel mit den gegebenen Ressourcen
unmöglich. Die richtige Auswahl von Testfällen kann die Fehlerentdeckung
effizient gestalten, erfordert aber viel Erfahrung und Intuition.
Beim Strukturtest von Programmbausteinen reicht es nicht aus, Testfälle
zu konstruieren, die sicherstellen, dass jede Anweisung mindestens einmal
ausgeführt wird, wie das folgende Beispiel zeigt:
if (x>=y) max=x else max = y;
if (z>=max) max=z else max = y;
Testdaten x,y,z mit z>=x>=y oder y>x oder y>z decken
nicht auf, dass das Maximum nicht berechnet wird, wenn x das Maximum
ist.
Testfälle sollten so ausgewählt werden, dass
-
jeder Programmzweig einmal durchlaufen wird,
-
für alle Funktionen geprüft wird, ob sie ihre Spezifikation erfüllen,
-
kleinster und größter Wert jedes Eingabedatums (Randwerte) berücksichtigt
wird,
-
jede Schleife mit minimaler und maximaler Durchlaufzahl geprüft wird,
-
alle Fehlerausgänge (Exceptions, Errors) geprüft werden,
-
die Reaktion des Testobjekts auf willkürliche Eingabedaten geprüft
wird (Stress-Test, Zufallstest).
Testumgebung bereitstellen
Eine brauchbare Testumgebung erlaubt es
-
Testobjekte wiederholt aufzurufen,
-
Eingabedaten bereitzustellen,
-
Simulation benötigter, aber nicht vorhandener externer Ressourcen
und ihrer Ergebnisse,
-
Testergebnisse anzuzeigen oder auszugeben.
Tests auswerten
Die Testfallbeschreibung legt fest, ob die Ergebnisse eines Testlaufes
mit den erwarteten Ergebnissen übereinstimmt. Am besten überlässt
man die Testauswertung eigens dafür entwickelten Programmen in der
Testumgebung.
Wird ein Fehler entdeckt, ist seine Ursache zu finden. Meist werden
dabei Hilfen zur Programmverfolgung und Zustandsüberwachung eingesetzt
(Debugger, ..).
Ist die Fehlerursache erkannt, gibt es oft mehrere Möglichkeiten,
ihn zu beheben. Vor der Entscheidung ist zu überlegen, welche Folgewirkung
die Korrektur auf andere Teile des Testobjektes hat. Dazu ist es oft notwendig,
den abstrakteren Entwurf zu studieren. Besondere Sorgfalt ist bei der Fehlerkorrektur
notwendig, allzu leicht werden neue Fehler eingeschleust. Nach jeder Fehlerkorrektur
sollen daher nicht nur alle Testfälle, die zur Entdeckung des Fehlers
führten, sondern auch alle anderen Testfälle, denen das Testobjekt
bisher unterzogen wurde, wiederholt werden.
Typische Fehler
Einige der häufigsten Fehler sind:
-
Sonderfälle vergessen (z.B. Division durch 0),
-
Randwerte nicht behandeln,
-
Feldgrenzen überschreiten,
-
Schleifen, die nicht abbrechen,
-
Variable nicht initialisieren,
-
logische Ausdrücke in Bedingungen, besonders im Zusammenhang mit dem
Negationsoperator, falsch bilden,
-
Parameterzahl und -typ in aktuellen und formalen Parameterlisten stimmen
nicht überein,
-
gemeinsame Daten unerlaubt benutzen.
Testdokumentation
Dazu gehört
-
Testplan
-
Beschreibung der Testfälle
-
Angaben zu den verwendeten Testumgebungen
-
Testergebnisse der einzelnen Testläufe
-
Zahl und Art der aufgedeckten Fehler
-
Beschreibung von Massnahmen zur Fehlerkorrektur
-
Benötigte Testläufe für jedes Testobjekt