Blog

Abgewandelt von "crossing the road" by Laura Bittner, http://flic.kr/p/6mCsiX (Lizenz: Creative Commons-BY 2.0)

Testing-Anti-Pattern-Kalender 2014 – Die Schlafmütze

In unserer monatlichen Blog-Reihe zu unserem Anti-Pattern-Kalender 2014 zum Thema Testing hier der Kandidat für den Monat Februar: Die Schlafmütze.

Wer langsam läuft, kommt auch ans Ziel. Nur eben drei Tage später

Auf die Schlafmütze müssen alle warten. Vor allem die Entwickler.

Das Problem

Automatisierte Softwaretests sollen dem Entwickler ein rasches Feedback bei der Entwicklung geben. Im Idealfall kann ein Entwickler nach jeder Code-Änderung direkt ermitteln, ob eine geänderte Komponente noch gemäß der Spezifikation funktioniert. Damit ist die Testausführung essenzieller Bestandteil des Entwicklungszyklus. Je länger die Ausführung von Softwaretests dauert, desto länger muss der Entwickler warten, bis er weiterarbeiten kann. Eine lange Testausführungsdauer führt unweigerlich dazu, dass Entwickler langsamer vorankommen oder die Test-Suites seltener ausführen und somit erst später auf mögliche Programmierfehler aufmerksam werden. das Test-Antipattern Schlafmütze beschreibt solche Testfälle mit unverhältnismäßig hoher Ausführungsdauer. Die folgenden Szenarien beschreiben typische Ursachen für langsame Tests.

Einmal um die Welt

Ein Klassiker der Test-Bremsen sind Test-Cases, deren getestete Komponenten mit dem Dateisystem interagieren oder über das Netzwerk (zum Beispiel mit einer Datenbank) kommunizieren. Einzelne Aufrufe sind kaum spürbar – es ist jedoch immer wieder erstaunlich, wie viel Zeit in der IO-Welt vertrödelt wird – wenn man überlegt, wie viele weitere Tests in dieser Wartezeit hätten ausgeführt werden können. Dazu kommt, dass einige Entwicklungsumgebungen Test-Suites standardmäßig sequenziell ausführen. Damit addieren sich solche Wartezeiten linear auf.

Die Lösung hierzu heißt Refactoring und konsequentes Mocking. Jegliche blockierende Operation sollte abstrahiert und im Test durch eine Mock-Implementierung ersetzt werden. Alternativ können auch Bibliotheken eingesetzt werden, die direkte Mock-Implementierung von IO-Systemklassen bereitstellen. In der Java-Welt gibt es je nach Anwendungsfall eine Vielzahl  hilfreicher Bibliotheken.

Die richtigen (wenigen) Tests

Test-Metriken wie die Line- oder Branch-Coverage sind wichtige Hilfsmittel für die Einschätzung der Qualität einer Code-Basis. In einigen Projekten findet man Richtlinien, die die Erfüllung einer solchen Metrik (etwa: „mindestens 90% branch coverage“) vorschreiben. Manch findiger Entwickler stellt sich diesen Anforderungen mit einer Brute-Force-Mentalität und erstellt Tests mit einer Vielzahl verschiedener synthetischer Parametersätze. Die einzelnen Parametersätze haben dabei nicht etwa eine fachliche Bedeutung, sondern dienen lediglich dem Selbstzweck der Erfüllung der Coverage-Anforderungen. Und die Ausführung dieser parameterisierten Tests kostet natürlich Zeit.

Deshalb sollte man sich schon bei der Auswahl der richtigen Test-Parameter einige Gedanken machen. Dabei können Methoden aus der strukturierten Testentwicklung wie Äquivalenzklassenanalyse und Randwertbetrachtung (Boundary value analysis) eingesetzt werden. Deren Ziel ist es, eine hohe Testabdeckung mit möglichst wenigen Tests zu erreichen.

Apropos Randwertbetrachtung: Parameter, die die Ausführungszeit einer zu testenden Komponente stark beeinflussen, sollten mit Bedacht gewählt werden. Ein Unit-Test, der eine Komponente eine Liste mit zehn Elementen korrekt sortieren lässt, muss dies nicht zwingend auch noch mit einer Liste probieren, die zehntausend Elemente enthält. Aspekte wie Laufzeitkomplexität sollten später im Systemtest gegen die tatsächlichen Anforderungen zum Reaktionszeitverhalten mit realistischen Daten getestet werden.

Jeder Test zu seiner Zeit

Eine lange Ausführungszeit kann auch ein Zeichen dafür sein, dass die falschen Dinge zur falschen Zeit getestet werden. Test ist nicht gleich Test. Abhängig von dem Scope eines Testfalls sind vor der eigentlichen Ausführung unterschiedliche Vorbereitungsschritte zu treffen. Mike Cohn beschreibt in seiner Testpyramide die folgenden Arten von Tests:

  • Unit-Tests testen eine isolierte Komponente/Klasse. Abhängigkeiten zu anderen Komponenten werden durch Mock-Objekte ersetzt. Das versteckte Verhalten einer Komponente, also die Interaktion mit abhängigen Komponenten, kann durch den Einsatz von Test-Spys geprüft werden.
  • Service-Tests testen technische Schnittstellen einer laufenden Anwendung. Dabei können öffentlich zugängliche Schnittstellen wie REST-Services, aber auch interne Schnittstellen getestet werden.
  • UI-Tests testen die Benutzeroberfläche einer laufenden Anwendung ohne Kenntnis oder Zugriff auf anwendungsinterne Schnittstellen. Es wird lediglich die direkte Interaktion mit der Benutzeroberfläche getestet.

Naturgemäß dauert die Ausführung solcher Service- und UI-Tests deutlich länger als die Ausführung von Unit-Tests, da vor der eigentlichen Testausführung die Anwendung bzw. große Teile davon gestartet, bzw. in einen definierten Ausgangszustand gebracht werden müssen. Gegebenenfalls ist die Anwendung auch nur auf einem entsprechend konfigurierten Test-System lauffähig.  In diesem Fall muss die Anwendung vor der Testausführung auch noch auf das Test-System gespielt werden, was ebenfalls Zeit kostet.

Aus diesen Gründen führen Entwickler während der Programmierung meist nur schnelle Unit-Tests aus und lassen Service- und UI-Tests nachgelagert durch eine Continuous-Integration-Umgebung ausführen. Damit Probleme zügig auffallen, sollte man also möglichst viel Funktionalität durch Unit-Tests abdecken und lediglich System- und Akzeptanztests gegen eine echte Umgebung laufen lassen.

Fazit

Dankenswerterweise sind Schlafmützen leicht aufzuspüren. In den meisten Entwicklungsumgebungen wird die Testausführungsdauer direkt angezeigt. Es ist schwierig, einen absoluten Richtwert für die Soll-Dauer eines Testfalls anzugeben; er sollte aber für die meisten Unit-Tests im niedrigen Millisekunden-Bereich liegen. Spätestens, wenn es heißt „MY CODE’s TESTING“, ist das Kind in den Brunnen gefallen.

Die meisten langsamen Unit-Tests entstehen, wenn die zu testende Komponente ihre Abhängigkeiten nicht sauber kapselt und zeit- oder arbeitsintensive Nebeneffekte auslöst, auch wenn diese für den Testfall gar nicht relevant sind. Solche teuren Operationen können durch Refactoring abstrahiert und im Test-Fall durch schnelle Mock-Implementierung ersetzt werden.

System- und Aktzeptanztests sind aufgrund des aufwendigen Test-Setups und der realen Anwendungskomplexität leider langsam. Daher sollte sich der Entwickler bei der Entwicklung nicht damit aufhalten und auf schnelle Unit-Regressionstests setzen. Diese großen Tests können nachgelagert durch ein Continuous-Integration-System ausgeführt werden, das den Entwickler im Problemfall benachrichtigt.

Holisticon AG — Teile diesen Artikel

Über den Autor

Antwort hinterlassen