Softwaretechnik: Unit-Testing
Vorwort
[Bearbeiten]Dieses Kapitel betrachtet die Prinzipien des Unit-Testing (dt. auch Modultest genannt) im Allgemeinen, also ohne Bezug auf eine Programmiersprache. Wenn in einem anderen Buch ein konkretes Unit-Test-Framework besprochen wird, kann hierauf verlinkt werden.
Problematik
[Bearbeiten]Vieles wäre besser, wenn Software fehlerfrei wäre. Leider tendiert sie dazu, Fehler zu besitzen. Daher wird Software normalerweise während der Entwicklung getestet; jedoch kann dies gerade bei großen Projekten ein mühsames Unterfangen sein. Schön wäre es, wenn der Computer doch selbst testen könnte.
Idee
[Bearbeiten]Die Idee besteht darin, dass ein Programm ein anderes Programm ausprobiert und die Ergebnisse analysiert. Das Ausprobieren kann dabei - nachdem es ja automatisiert werden kann - eine große Menge von Tests durchführen lassen, mehr, als ein Mensch manuell durchführen könnte.
Dieses Prinzip, das Unit-Testing, kommt ursprünglich aus Extreme Programming, gewinnt jedoch zunehmend auch außerhalb derer an Bedeutung.
Black-Box-Test
[Bearbeiten]Prinzip
[Bearbeiten]Das Test-Programm erzeugt eine Umgebung (setzt in der Regel globale Variablen und Instanzvariablen) und ruft die zu testende Funktion mit bestimmten Parametern auf. Danach überprüft es den eventuellen Rückgabewert der Funktion und die Umgebung auf ihre Änderungen.
Es ist sinnvoll (und meistens auch möglich), dass das Test-Programm in derselben Sprache geschrieben wird, wie das zu testende Programm.
Black-Box-Tests sollten anhand der Spezifikationen (z. B. Parameter, Wirkung, Rückgabewerte der Funktionen) des zu testenden Programms geschrieben werden, um unnötige Abhängigkeiten von der eigentlichen Implementierung zu vermeiden. Des Weiteren empfiehlt es sich, diese Tests geradezu bösartig - sogenannte Negativ-Tests - (viele Tests, falsche Parameter, Randfälle... all die schönen Dinge, die fehlerhafte Programme gerne überfordern...) zu schreiben; es wird gerne empfohlen, dass die Tests von einem anderen Programmierer geschrieben werden, als dem, der die eigentliche Implementierung übernimmt - sonst droht die Gefahr, dass der Test zu "nett" wird.
Beispiel (Funktion mit Seiteneffekten)
[Bearbeiten]Getestet werden soll eine Funktion, die den Wert der globalen Variable A um 1 verringert. Dazu setzt das Test-Programm die Variable A auf einen beliebigen, aber festgelegten Wert, z.B. 3. Dann ruft es die Funktion auf und überprüft, ob A jetzt den Wert 2 besitzt; ist dies nicht der Fall, ist die Funktion fehlerhaft, und das Test-Programm meldet dies.
Beispiel (Funktion mit Rückgabewert)
[Bearbeiten]Soll beispielsweise eine Funktion getestet werden, die 60 durch ihren Parameter dividiert. Dann ist zu erwarten, dass die Funktion 2 zurückgibt, wenn der Parameter 30 ist; ebenso, dass 15 zurückgegeben wird, wenn der Parameter 4 ist.
Das Test-Programm ruft also die Funktion mit 30 auf und überprüft, ob der Rückgabewert 2 ist. Danach ruft es dieselbe Funktion mit 4 auf und überprüft, ob der Rückgabewert 15 ist. Sollte mindestens ein Ergebnis nicht stimmen, meldet es dies als Fehler in der Funktion an den Programmierer.
Beispiel (Fehler)
[Bearbeiten]Die oben definierte Funktion mit Rückgabewert hat einen Fall, bei dem ein Problem auftreten kann: Wenn der Parameter 0 ist. Um dies zu testen, kann das Test-Programm 0 einsetzen und überprüfen, ob das gewünschte Verhalten auftritt (Rückgabewert, ob Exceptions auftreten...) - falls nicht, ist das ein Fehler im zu testenden Programm, und gehört gemeldet.
White-Box-Test
[Bearbeiten]Unit-Tests können auch zu den White-Box-Testtechniken gezählt werden. Für Black-Box-Tests werden u. A. die Spezifikation/Dokumentation benutzt, um die Funktionsweise abzuleiten. Falls aber keine Dokumentation vorhanden ist, wie eigentlich vom ursprünglichen Extreme Programming propagiert, muss man die interne Struktur des Moduls zu "Rate ziehen". Auch werden Tests bezüglich der nicht-funktionalen Qualitätsmerkmale durchgeführt, wie z. B. Effizienz, da eine spätere Korrektur erstens mehr kostet, des Weiteren vielleicht gar nicht mehr das verursachende Modul identifizierbar ist oder auch einfach schlichtweg die Zeit dazu fehlt.
Testgetriebene Entwicklung (test-driven development)
[Bearbeiten]Das Prinzip besteht darin, erst die Spezifikation, dann die Tests, und dann die Implementierungen zu schreiben. Hierbei wird solange implementiert, bis die Tests nicht mehr fehlschlagen.
Anmerkung am Rande
[Bearbeiten]In den meisten Fällen kann kein noch so strenger Test die Richtigkeit eines Programms beweisen. Tests dienen nur als Hilfsmittel zur Vermeidung von Fehlern, insbesondere deren Wiederauftreten.
Übrigens: Da Tests auch Software sind, können diese auch fehlerhaft sein.
Unit-Test-Frameworks
[Bearbeiten]Es gibt inzwischen für die meisten Programmiersprachen spezielle Frameworks für Unit-Testing. Es handelt sich meist um Programmbibliotheken, deren Funktionen zum Schreiben von Test-Programmen (welche sich dann Test-Cases nennen) und zum Analysieren der Ergebnisse nützlich sind.
Da diese von der jeweiligen Sprache abhängig sind, finden sich Beispiele für jede Sprache bei den jeweiligen Sprachen, oder (siehe unten) beim Wikipedia-Artikel.