Unit-Tests in der Software-Entwicklung

Nach der ausführlichen Betrachtung von Akzeptanztests in der Software-Entwicklung widmen wir uns nun dem anderen Ende im Spektrum der Software-Tests: dem Testen konkreten Codes, im Extremfall einzelner Zeilen. Ein Unit-Test (auch Modultest oder Komponententest) ist ein ganz basaler Test einer einzelnen konkreten Funktionalität bzw. Funktion oder Methode (Unit).

Eigenschaften und Prinzipien

Was machen Unit-Tests aus und unterscheidet sie von anderen Testarten? Die grundlegende Eigenschaft von Unit-Tests ist die Isoliertheit. Ein Unit-Test führt nur eine konkrete Funktion aus und klammert sämtliche Einflüsse von außen (Kommunikation mit anderen Systemen, Aufrufe weiterer Funktionen, etc) aus. Als zentrale Stichwörter zur Gewährleistung dieser Isoliertheit seien hier z.B. das Mocking als testseitige Technologie sowie Parametrisierung und das Prinzip der einfachen Verantwortlichkeit als Programmiermethodiken bei der Entwicklung des testbaren Codes erwähnt. Übrigens ist es diese Isoliertheit (und damit das entscheidende Merkmal eines Unit-Tests), die nach meiner Erfahrung oft stark unterschätzt und vernachlässigt wird. Es gibt Entwickler, die sich über die Konsequenzen einer nicht existierenden Isoliertheit gar nicht im Klaren sind. Groß ist dann die Verwunderung, wenn durch eine kleine Änderung in Code A plötzlich die Tests von Code C, D, E und K fehlschlagen.

Ihr Potenzial zur Sicherstellung der Korrektheit von Funktionalitäten entfalten Unit-Tests besonders auch dann, wenn nicht ausschließlich sogenannte Gut-Fälle, sondern auch explizit Fehlverhalten getestet wird. Man spricht hierbei von Sonder- oder Grenzfällen. In der testgetriebenen Entwicklung (Test Driven Development, TDD) wird dieses Prinzip allerdings etwas vernachlässigt. Die Herausforderung besteht insbesondere darin, Grenzfälle zunächst als solche zu erkennen und zudem einzuschätzen, wie realistisch deren Eintreten ist. Es macht natürlich keinen Sinn, jeden denkbaren Grenzfall zu berücksichtigen. Beim Einsatz von Tests zur Analyse und Korrektur aufgetretener Fehler/Bugs werden genau diese Fehlerfälle tatsächlich systematisch abgetestet um sie dann korrigieren zu können.

Weiterhin werden Unit-Tests laufend bzw. ständig ausgeführt, um Korrektheit des Codes über die Zeit hinweg sicherzustellen. Das erfordert sowohl Disziplin beim Entwickler (der regelmäßig mindestens seinen eigenen Code testen muss) als auch Automatisierung (i.d.R. durch die kontinuierliche Integration/Continous Integration). Dabei lautet das zentrale Prinzip hinter dem Unit-Test: Die Schnittstelle, nicht die Implementierung wird getestet, nicht der Code steht im Fokus, sondern was er tut bzw. welches Ergebnis er liefert.

Wie setzt man die Tests sinnvoll ein?

Für den sinnvollen und fruchtbaren Einsatz von Unit-Tests ist eine Methodik notwendig. Einen solchen methodischen Rahmen bietet die bereits erwähnte testgetriebene Entwicklung als Bestandteil des Modells eXtreme Programming. Grundsätzlich gilt, dass man mit allen Testarten (also auch Akzeptanztests, Integrationstests usw.) testgetrieben entwickeln kann. Hier möchte ich aber nur auf die Unit-Tests eingehen, wie es auch in der einschlägigen Literatur häufig gehandhabt wird.

Die an sich einfache Idee hinter dem Konzept: Der zu testende Code wird durch die produktive Verwendung (im Rahmen eines Tests) überhaupt erst entwickelt. Zuerst wird also der Test geschrieben, der die gerade entwickelte Funktion verwendet. Auf dieser Grundlage entsteht dann der eigentliche Code.

Warum sind nachträgliche Tests schlecht?

Wer wirklich testgetrieben entwickeln will, schreibt die Tests vor dem eigentlichen Code. Der genau umgekehrte Weg wird in der Praxis aber häufig anzutreffen sein, denn einerseits werden Unit-Tests oft noch als notwendiges Übel, lästige Pflicht oder Zeitverschwendung angesehen. Andererseits fehlt vielleicht auch oft das genaue Wissen um das methodische Vorgehen und die Erkenntnis des Sinns und Nutzens. Und genau in diesen Situationen stellt sich vielleicht obige Frage.

Die Antwort bringt Frank Büchner in einem ausführlichen, interessanten Artikel zum Thema auf den Punkt:

Durch aus dem Code abgeleitete Testfälle kann man keine Auslassungen im Code entdecken! Wurde z.B. vergessen, eine Abfrage zu implementieren, kann natürlich aus dem Code auch kein Testfall abgeleitet werden, der diese Abfrage prüft. Auch wenn man 100% Coverage erreicht, ist die Testaussage zweifelhaft, denn der Code wurde als vollständig vorausgesetzt.

Daher werden Unit-Tests nach der testgetriebenen Methodik zyklisch entwickelt. Das Vorgehen folgt dem Muster: Test a little, code a little. Es wird ein Test geschrieben, der zunächst fehlschlägt. Nun wird genau soviel Produktivcode implementiert, dass der Test erfolgreich durchläuft. Anschließend werden Test und Produktivcode refaktorisiert.

Vorteile

Unit-Tests erleichtern und beschleunigen die Fehlerfindung signifikant und machen Probleme in einem frühen Stadium der Entwicklung sichtbar. Wird ein Fehler entdeckt, muss er notwendig in der soeben getesteten Unit liegen. Das Vorgehen hat entscheidende Vorteile

  • Schnelle Rückmeldung durch regelmäßige Ausführung
  • Konzentration auf das Wesentliche
  • Nicht nur Code schreiben, sondern auch mit dem Code arbeiten, also ständiges Überdenken der eigenen Methodik und dadurch Optimierung des Codes
  • Hohe Testabdeckung möglich
  • Software-Design wird automatisch testbar und in der Regel zudem auch hochwertiger
  • Die Verwendung des Codes ist durch die Tests direkt dokumentiert, bei konsequentem Vorgehen und Verwendung können einige Dokumentationen entfallen

Vorurteile und Akzeptanzprobleme

Doch sinnvolle, zielführende, methodisch saubere Unit-Tests sind keine Selbstläufer. Es entstehen einige Herausforderungen, die insbesondere den Entwickler und seine Arbeitsweise betreffen. Zunächst geht mit der Etablierung von Unit-Tests die Durchsetzung eines neuen Paradigmas einher, das etablierte Vorgehensweisen in Frage stellt. Die Akzeptanz muss also wachsen.

Gängige Akzeptanzprobleme im Zusammenhang mit Unit-Tests betreffen die folgenden Aspekte:

  • Tests werden oft nur der Tests wegen durchgeführt.
  • Der initiale Zeitaufwand ist fast immer vergleichsweise hoch.
  • Insb. Webanwendungen lassen sich schwer testen (Nutzerinteraktivität, Browserverhalten, etc.).
  • Nicht für jedes Problem sind Unit-Tests die Lösung (wobei TDD ja nicht nur von Unit-Tests spricht).
  • Disziplin ist erforderlich.
  • Erfahrung muss erworben werden. (Hier haben sich z.B. Coding Dojos/Code Katas bewährt.)

Grenzen

Grundsätzlich sollte die Ausführung von Unit-Tests automatisiert werden, um eine gleichbleibende Qualität über viele Releases hinweg zu gewährleisten. Die Continous Integration bietet hierfür wohl die wohl einfachste Möglichkeit.

Freilich stoßen die testgetriebene Entwicklung und Unit-Tests auch an Grenzen. Entwickler könnten Unit-Tests falsch einsetzen, eine Garantie auf Fehlerfreiheit besteht nicht. Es entsteht ein gewisser, nicht ganz unerheblicher Initialaufwand, der in der Kalkulation von Software-Projekten berücksichtigt werden muss.

Und nicht zuletzt muss klar sein, dass sich Unit-Test nur für reine Funktionstests eignen; Performance, Usability und andere Faktoren lassen sich mithilfe von Unit-Tests nicht messen.

Fazit

Unit-Tests sind nicht nur im Rahmen des Entwicklungsprozesses sinnvoll, sondern auch bei der Fehlerkorrektur. Dabei gilt das Prinzip: Pro Bug ein Test. Die Erfahrung zeigt, dass Unit-Tests beim Bugfixing sowohl bei der Eingrenzung des Fehlers und als auch bei der Fehlerbehebung hilfreich sind.

Somit sind Unit-Tests mächtige Werkzeuge, die sich vor allem in der iterativen Software-Entwicklung etabliert und bewährt haben. Auf Entwicklerseite sind sicherlich ein Umdenken und das Verinnerlichen neuer Vorgehensweisen erforderlich. Diese sind aber auf jeden Fall im Sinne einer möglichst hohen Code-Qualität und insbesondere der möglichst reibungsarmen iterativen Erweiterbarkeit der Software.

Wie die testgetriebene Entwicklung genau abläuft kann man sich übrigens z.B. auf YouTube ansehen: Viele Entwickler laden Videos eigener Code Katas hoch anhand derer man sich das Eine oder Andere abschauen kann. Hier ist ein schönes Beispiel.

Ihr neues Projekt soll mit modernsten Technologien entwickelt werden? Sie möchten ein bestehendes System erweitern oder eine Software-Plattform migrieren? Sie benötigen Schnittstellen zwischen Anwendungen im Unternehmen? //SEIBERT/MEDIA ist der richtige Partner. Wir legen größten Wert auf iterative Erweiterbarkeit, Performanz, Skalierbarkeit, Testbarkeit und Plattformunabhängigkeit und schaffen so individuelle High-End-Software-Lösungen, die sich auch im Nachhinein flexibel ausbauen und verändern lassen. Bitte sprechen Sie uns unverbindlich an!

Weiterführende Informationen und Quellen

Unsere spezielle Seite zum Thema "Agile"
Unsere Seite rund um die Software-Entwicklung mit Java
Akzeptanztests in Scrum-Projekten: Theorie und Praxis

Was ist testgetriebene Entwicklung
Kent Beck: Test Driven Development by Example


Mehr über die Creative-Commons-Lizenz erfahren