Home Blog Software Architektur Erfahrungen mit JUnit

Erfahrungen mit JUnit

Einige Entwickler vertreten den Standpunkt, dass Unit Tests zwar nützlich sind, aber zuviel Entwicklungs-Zeit dabei verloren gehe, Tests zu schreiben. Dem gegenüber stehen Entwickler, welche von Unit-Tests so überzeugt sind, dass sie am liebsten die gesamte Funktionalität der Applikation durch Unit-Tests abdecken wollen und entsprechend Aufwand betreiben, um dieses Ziel zu erreichen.

Beide Ansichten haben Ihre Berechtigung: wir wollen einerseits so wenig wie möglich Entwicklungszeit für das Schreiben von Unit-Tests aufwenden, andrerseits so viel Funktionalität wie möglich durch Unit-tests automatisch überprüfen.

Damit es gelingt in kurzer Zeit Unit-Tests zu schreiben, müssen die Tests eine naheliegende Voraussetzung erfüllen: sie müssen einfach gehalten sein. D.h. es darf nicht sein, dass ich für einen Test komplexe Logik programmieren muss, um zu meinem Testergebnis zu kommen. Testcode soll trivial sein.

Kernfunktionen einer Applikation sollen durch Unit-Tests abgedeckt werden. Was einleuchtend klingt ist in der Praxis nicht ganz einfach umzusetzen. Kernfunktionen von komplexen Applikationen verteilen sich oft über mehrere Schichten (Datenbank-, Businesslogik-, UI-Schicht). Soll ein Unit-Test nun versuchen eine Funktion durchgängig, d.h. über alle Schichten hinweg zu testen? Nein. Aus folgenden Gründen:

  1. Ein solcher Test ist nicht mehr trivial und benötigt fast sicher viel mehr Entwicklungszeit als ein Test der nur Abhängigkeiten zu einer einzigen Schicht oder, noch besser, einer einzigen Klasse hat.
  2. Ganz sicher muss ich diverse clevere Tricks anwenden, um meinem Test zum Laufen zu bringen. Eventuell benötige ich Simulatoren, Dummy-Implementierung, etc. Vielleicht muss ich sogar die Sichtbarkeit einiger Methoden oder Klasseneigenschaften verändern, damit die Test-Implementierung gelingt.

Gerade der zweite Punkt führt dazu, dass Unit-Tests eher lästig denn hilfreich werden. Wenn Tests explizit interne Eigenschaften von fremden Klassen manipulieren, um den Test zu ermöglichen, bedeutet dass, das ich die fremde Klasse nicht mehr einfach ändern kann, ohne dass ich mich mit der teilweise recht komplexen Test-Logik auseinandersetzen muss. Zudem will ich die Implementierung einer Klasse jederzeit ändern können, solange die API Methoden ihren Vertrag immer noch erfüllen.

Unit-Tests sollen deshalb folgende Regeln einhalten:

  •  Ein Test soll wenig bis keine Abhängigkeiten zu anderen Klassen als der zu testenden Klasse haben
  •  Benötigt ein Test Methoden von einer Klasse die nicht direkt mit dem Test zu tun haben, so darf er nur das API der Klasse verwenden (also public Methoden und Eigenschaften), aber keine "Innereien" der Klasse
  •  Die Sichtbarkeit von Eigenschaften oder Methoden darf nicht verändert werden nur um eine Klasse testbar zu machen (Ausnahme: Bei der direkt zu testenden Klasse ist es erlaubt, private Eigenschaften und Methoden auf Package-Sichtbarkeit zu ändern).

Man kann nun argumentieren, dass unter diesen Voraussetzugen keine „durchgängigen“ Tests von Funktionen mehr realisierbar sind: das stimmt auch. Man kann das aber kompensieren, in dem man dafür sorgt, dass die Klasse die man schreibt, sich jederzeit in einen bestimmten Zustand versetzen lässt, und dann auf Methoden-Ebene testen, ob das richtige für den jeweiligen Zustand gemacht wird. Mit diesem Vorgehen kann man alle Zustände abdecken die auch in einem durchgängigen Test vorkommen und hat keinerlei oder wenig Abhängigkeiten zu fremden Klassen. Wenn jede Klasse bzw. Methode für sich alleine das richtige macht, wird sie auch im grossen Ganzen das richtige tun.

Ein weiterer Grund der gegen durchgängige Unit-Tests spricht: auch ein "durchgängiger" Unit-Test ist keine Garantie für korrektes Funktionieren der Applikation, da Unit-Tests eben nicht das richtige Leben sind. Das User Interface existiert nur als Modell, Datenbank-Zugriffe werden simuliert, etc. Und natürlich können genau diese Komponenten ebenfalls Programmierfehler enthalten, welche eine Funktion unbrauchbar machen, obwohl der Unit-getestete Teil Programmlogik einwandfrei funktioniert. Deshalb: Ein Unit-Test ersetzt nie einen Akzeptanz-Tests der von einem Bediener durchgeführt wird und soll deshalb auch nicht versuchen die selben Ziele zu erreichen.

Fazit: Viele, einfache Unit-Tests bringen mehr als komplexe, durchgängige. Letztere verschlingen zudem wertvolle Entwicklungszeit und können zum Hindernis werden, wenn sie interne Eigenschaften oder Methoden einer testfremden Klasse verwenden.

 

Kommentar schreiben


Sicherheitscode
Aktualisieren