Warum testen wir?
Softwaretests sind Teil des Entwicklungsprozesses jedes Programms. Die Frage lautet nur, wann und wo sie durchgeführt werden. Im schlimmsten Falle findet der Test im Rechner des Kunden in einem Passagierflugzeug über dem Pazifik statt. Diese „kundenseitigen“ Softwaretests sind meist eher unpraktisch und kosten intensiv, da im Falle eines Fehlschlags meist finanzielle und Reputationsverluste auftreten. Des Weiteren sind die Rückmeldungen meist nicht standardisiert und schwierig zu interpretieren. Aus diesen Gründen sollte man die Testphase zeitlich nach vorne verlagern.
Für Entwickler vereinfachen Tests die Fehlerbehebung, da sie Informationen über den Zustand und die Funktionalität des Programms enthalten. Sie ermöglichen uns, die Quelle des Fehlers genauer zu lokalisieren.
Was testen wir?
Bevor wir uns fragen, wie wir testen, sollten wir definieren, was wir testen. Im Grunde nimmt ein Programm gewisse Eingaben entgegen und produziert anschließend Ausgaben, die auch in einem Kontrollkreis integriert sein können. Dies ermöglicht es uns, das Programm als eine mathematische Funktion aufzufassen, welche auf einen Eingabevektor angewandt einen Ausgabevektor erzeugt. Bisher konnte ich jedes informatische Problem derart beschreiben, auch wenn ich in diese Ein- und Ausgabevektoren physische Objekte, wie Roboterarme, aufnehmen musste.
Wir können das Programm nun in Unterfunktionen zerlegen. Über ihre Ein- und Ausgaben formen diese Funktionen nun, einen möglicherweise zyklischen Graphen, der unser gesamtes Programm beschreibt.
Wie testen wir?
Nach dieser Modellierung unseres Systems als verbundene Funktionen, können wir unsere Softwaretests jetzt direkt aus diesen herleiten. Die einzige Aufgabe unserer Tests ist zu prüfen, ob die Funktionen die Eingaben auf die gewünschten Ausgaben abbilden. Praktisch können wir natürlich nicht für alle möglichen Eingaben prüfen, weshalb wir Testwerte verwenden. Je nach Problem können wir diese automatisch zufallsgenerieren oder müssen sie „händisch“ erstellen.
Um die Testwerte zufällig zu erzeugen, müssen wir entweder eine einfach zu invertierende Funktion oder eine einfach zu testende Untergruppe finden. Beide Ansätze enthalten Risiken, da wir entweder die Werte außerhalb der Untergruppe ungetestet lassen oder an der Berechnung der inversen Funktion scheitern können. Ein gutes Beispiel ist eine Quadratwurzelfunktion: Um sie zu testen, könnten wir einfach eine beliebige Zahl quadrieren und überprüfen ob die Ausgabe der Quadratwurzelfunktion unter Berücksichtigung von Rundungsfehlern mit dieser übereinstimmt.
Test-Ebenen
Unserem Modell gemäß sind in unseren Funktionen kleinere Unterfunktionen eingebettet. Folglich können wir unsere Softwaretests in eine Baum-förmige Hierarchie einbetten. Die Tests in dieser Hierarchie können wir dann in die folgenden Gruppen einteilen:
-
kleine, gewissermaßen atomare Tests, welche die Funktion einzeln abdecken
-
umfassendere Tests, die Komponenten abdecken
-
Integrationstests, welche die Interaktion zwischen verschiedenen Komponenten testen
-
Systemtests, die das gesamte System von Installation bis zum Abschluss des Arbeitsvorgangs testen
Einige dieser Tests können, da sie relativ einfach und schnell durchzuführen sind, automatisiert werden. Andere, da sie komplexere Eingaben oder physische Komponenten benötigen, hingegen nicht. Das Ziel der automatisierten Tests sollte das frühzeitige Auffinden von Fehlern sein, bevor diese undurchschaubares Fehlverhalten auslösen. Die manuellen Softwaretests sollen lediglich als letzter Schirm die verbleibenden Fehler auffangen.
Die Fehler der Anderen
Häufig werden wir nicht in der Lage sein, das gesamte Programm zu testen, da einige Programmteile von Fremdanbietern entwickelt wurden, weshalb wir keinen Zugriff auf den Quellcode. Die beeinflusst nicht die Art wie wir testen, sondern nur den Umfang der Tests, da nun der kleinste Test bereits eine komplexe Komponente wie einen Roboterarm, ein neuronales Netz oder eine Kamera testet.
Diese gesteigerte Komplexität macht das Erstellen von Softwaretests anspruchsvoller. Jedoch können wir ausnutzen, dass der Fremdcode im Grunde sein Verhalten nicht verändern sollte: wir müssen ihn also nur einmal auf seine Funktionalität testen, bevor wir ihn verwenden. Anschließend sollte sein Verhalten gleich bleiben.
Weitere allgemeine Tipps für Softwaretests:
-
Stellen Sie sicher, dass Ihre Tests vorhersehbar und reproduzierbar sind, damit sie Fehlverhalten gezielt untersuchen.
-
Entwerfen Sie Ihre Tests vor Ihren Funktionen, Modulen oder Programmen, damit sie nicht von Ihrer Implementierung beeinflusst werden.
-
Entwerfen Sie Ihre Tests unabhängig voneinander, sodass ein Test nicht die Ergebnisse des anderen beeinflusst.
-
Versuchen Sie so breit beziehungsweise so viel wie möglich zu testen.
-
Wenn Sie einen Fehler finden, entwerfen Sie erst einen Test, der diesen erkennt, um sich vor einem erneuten Auftreten in späteren Interpretationen des Programms zu schützen.
Wenn Sie noch mehr über Tests oder Test-Driven-Development erfahren wollen, empfehle ich “Growing Object-Oriented Software, Guided by Tests” von Steve Freeman and Nat Pryce.