Wikipedia · einfach zusammengefasst · Stand
Modultest
Ein Modultest (auch von englisch unit test als Unittest oder als Komponententest bezeichnet) ist ein Softwaretest, mit dem einzelne, abgrenzbare Teile von …
Inhalt5 Abschnitte
Begriff und Rolle im Testprozess
Ein Modultest, auch Unittest oder Komponententest genannt, ist ein Softwaretest für einzelne, klar abgrenzbare Teile eines Computerprogramms. Solche Teile können ausgewählte Codeabschnitte, Module, Unterprogramme, Units oder Klassen sein. Ziel ist es, die technische Lauffähigkeit und die Korrektheit fachlicher Teilergebnisse nachzuweisen. Häufig führt der Softwareentwickler selbst diese Tests durch.
Der Begriff bezeichnet auch eine frühe Teststufe, in der die inneren und detailliertesten Komponenten einer Software geprüft werden. Nach dem Software Validation & Verification Plan sind Modultests nur bei Modulen mit geringer Kritikalität nicht notwendig, also bei Modulen, deren Fehler den Benutzern nur geringe Unannehmlichkeiten bereiten.
Modultests sind wichtig, weil Algorithmen auf Unitebene meist eine begrenzte Komplexität und klar definierte Schnittstellen haben. Dadurch lassen sie sich mit relativ wenigen Testfällen weitgehend vollständig testen. Das erleichtert die anschließenden Integrationstests: Dort kann man sich stärker auf das Zusammenspiel größerer Funktionsteile oder der ganzen Anwendung konzentrieren, statt alle Detailfälle einzelner Module erneut umfassend zu prüfen. Vergleichbar ist dies mit einem Gerät, das erst als Ganzes getestet wird, wenn die Funktionsfähigkeit seiner Einzelteile als gesichert gilt.
Testfälle und Grundaufbau
Modultests zählen grundsätzlich zu den White-Box-Tests. Das bedeutet: Bei der Definition der Testfälle ist der zu testende Quellcode bekannt. Die Spezifikation der Software wird vor allem verwendet, um die erwarteten Ergebnisse, also Soll-Ergebnisse, festzulegen. Prinzipiell sollen alle Teile des Quellcodes mindestens einmal ausgeführt werden.
Zur Planung der Testfälle können Verfahren wie Anweisungsüberdeckung, Zweigüberdeckung oder Pfadüberdeckung dienen. Anweisungsüberdeckung prüft, ob einzelne Anweisungen ausgeführt wurden. Zweigüberdeckung betrachtet unterschiedliche Verzweigungen im Kontrollfluss. Pfadüberdeckung bezieht sich auf mögliche Ausführungspfade durch den Code. In der Praxis versucht man, das gewählte Überdeckungsziel mit möglichst wenigen Testfällen zu erreichen, weil Modultests dauerhaft gepflegt werden müssen.
Der übliche Aufbau eines Modultests besteht aus drei Schritten: Zuerst wird ein Ausgangszustand initialisiert. Danach wird die zu testende Operation ausgeführt. Abschließend wird das tatsächliche Ergebnis, also das Ist-Ergebnis, mit einem aus der Spezifikation abgeleiteten Sollwert verglichen. Test-Frameworks stellen dafür assert-Methoden bereit; „assert“ bedeutet hier ungefähr „feststellen“ oder „versichern“.
Isolierung und Hilfsobjekte
Ein Modultest prüft ein Modul isoliert, also möglichst ohne echte Interaktion mit anderen Modulen. Wenn das Testobjekt andere Bestandteile benötigt, können diese durch Hilfsobjekte simuliert werden. Das betrifft zum Beispiel Datenbanken, Dateien, Backendsysteme, Unterprogramme oder andere Module.
Hilfsobjekte lassen sich vor allem danach unterscheiden, welche Rolle sie ersetzen. Ein Stub ersetzt ein aufzurufendes Modul; dabei ist der Prüfling das aufrufende Modul. Ein Driver ersetzt dagegen den Aufruf oder die Umgebung eines zu testenden Moduls oder Unterprogramms; dabei ist der Prüfling die Unterroutine. In objektorientierten Programmiersprachen gibt es weitere genauere Kategorien, etwa Mock-Objekte.
Wie vollständig ein Hilfsobjekt das Verhalten des Originals nachbildet, muss beim Testfalldesign berücksichtigt werden. Dazu gehören zum Beispiel Plausibilitätsprüfungen oder die Rückgabe von Antwortcodes. Solche Hilfsobjekte können als Stellvertreter implementiert und durch Inversion of Control bereitgestellt werden. Dadurch lässt sich ein Modul oft einfacher testen als in einer bereits vollständig integrierten Anwendung, weil Abhängigkeiten nicht im selben Maß berücksichtigt werden müssen.
Isolierte Modultests sind außerdem möglich, wenn benötigte Komponenten noch nicht verfügbar sind. Vollständige Tests mit allen Komponenten in ihrer Originalversion gehören dagegen zu späteren Integrations- und Systemtests. Dort können auch Fehler entdeckt werden, die im Modultest verborgen bleiben, etwa wenn Testobjekt und Hilfsroutine dieselbe falsche Annahme enthalten.
Vertrag statt Interna und Automatisierung
Nach dem Design-by-contract-Prinzip sollen Modultests möglichst nicht die internen Details einer Methode prüfen, sondern ihren „Vertrag“, also ihre von außen sichtbaren Wirkungen. Dazu zählen Rückgabewerte, Ausgaben, Zustandsänderungen und Zusicherungen. Wenn Tests zu stark auf interne Details zielen, können sie fehlschlagen, obwohl sich das äußere Verhalten der Methode gar nicht geändert hat.
Im Artikel wird dafür in der Regel Black-Box-Testing empfohlen. Dabei beschränkt man sich auf die Prüfung der externen Auswirkungen. Das steht in Spannung zur vorher genannten Einordnung von Modultests als White-Box-Tests: Für die Testfalldefinition kann der Code bekannt sein, zugleich sollen Tests idealerweise nicht unnötig an interne Implementierungsdetails gebunden werden.
Mit agilen Softwareentwicklungsmethoden und besonders mit testgetriebener Entwicklung ist es üblich geworden, Modultests möglichst automatisiert auszuführen. Dafür werden mit Test-Frameworks wie JUnit Testprogramme geschrieben. Die Frameworks rufen einzelne Testklassen auf, führen deren Komponententests aus und geben meistens eine grafische Zusammenfassung der Testergebnisse aus. Automatisierte Modultests können einfach und kostengünstig ausgeführt werden und helfen, neue Programmfehler schnell zu finden.
Nutzen, Aufwand und Grenzen
Modultests haben mehrere Vorteile. Automatisierte Unittests können im Schnitt 30 % der Fehler erkennen. Bei testgetriebener Entwicklung können im Schnitt 45 % und im besten Fall 85 % der Fehler vermieden werden. Weil Fehler schon während der Entwicklung entdeckt werden, sind die vermiedenen Fehlerkosten gemäß der Rule of Ten deutlich höher als bei späteren Teststufen. Dadurch gelten Unittests als besonders effiziente Teststufe.
Weitere Vorteile sind die genaue Eingrenzung von Fehlern, schnellere Fehlerbehebung und eine Art lebende Dokumentation. In Verbindung mit sinnvoll benannten Objekten im Sinne von Clean Code können zusätzliche Dokumentationsmaßnahmen teilweise entfallen. Da einzelne Module nur wenige mögliche Codeausführungspfade besitzen, müssen weniger kombinatorische Ausführungspfade berücksichtigt werden als bei anderen Testarten. Außerdem laufen Modultests oft um mehrere Größenordnungen schneller als andere Testarten und können daher häufig oder kontinuierlich ausgeführt werden. Wenn ein Fehler durch einen Test abgesichert wird, soll verhindert werden, dass derselbe Fehler erneut auftritt. In mittleren bis großen Softwareprojekten können Fehlerreduktion, schnelle Tests und geringe Abhängigkeiten die Entwicklung beschleunigen und Code leichter änderbar halten.
Dem stehen Nachteile gegenüber. Neue Funktionalität erfordert nicht nur die Implementierung der Funktion, sondern auch die Vorbereitung oder Definition der passenden Tests. Dadurch kann ein mehrfacher Implementierungsaufwand entstehen. Bei Änderungen müssen oft auch Tests angepasst werden, was besonders bei Prototypen mit schnell wechselnder Codebasis hinderlich sein kann. Außerdem kann in IDEs schwerer erkennbar sein, ob eine Funktionalität außerhalb der Tests noch verwendet wird. Wenn Tests untereinander Abhängigkeiten haben, etwa durch gemeinsame Testdaten, können Änderungen viele Tests beeinflussen und den Änderungsaufwand mit wachsender Codebasis exponentiell erhöhen.
Modultests können die Fehlerfreiheit eines Moduls nicht garantieren. Sie finden nur Fehler, zu deren Entdeckung die vorhandenen Tests geeignet sind. Ein Modul, das „grün“ testet, ist also nicht automatisch fehlerfrei. Der Artikel weist darauf hin, dass der Abschnitt zu den Grenzen nicht hinreichend belegt ist. Problematisch ist auch, wenn nur so lange getestet wird, bis alle Tests erfolgreich sind. Denkfehler können unentdeckt bleiben, wenn dieselbe Person Modul und Test entwickelt; auch testgetriebene Entwicklung schließt das nicht vollständig aus. Im Extreme Programming kann „Test Ping-Pong“ helfen, bei dem sich Entwickler beim Schreiben von Funktionalität und Tests abwechseln. Außerdem können bei Modultests Anti-Pattern entstehen, also wiederkehrende ungünstige Vorgehensweisen, die vermieden werden sollten.