Montag, 27. August 2012

Was macht gute Modultests aus?

Coverage
Hohe Code-Coverage - gute Qualität?
CC-BY-SA Sebastian Dietrich
Gute Modultests (engl. Unit-Tests) können einen Großteil der Fehler finden, die sich während der Entwicklung eingeschlichen haben. Üblicherweise geht man davon aus, dass mit Modultests 80 - 90% aller Fehler gefunden werden können. Dies ist jedoch nur dann möglich, wenn diese Modultests (wie Regressiontests) laufend ausgeführt werden und eine ausgezeichnete Qualität aufweisen.

Laufende Ausführung aller Modultests ist spätestens seit Einzug von Unit-Test Frameworks wie JUnit keine Seltenheit mehr. Kaum einem Projekt fehlen heutzutage Mechanismen um sicherstellen, dass alle Modultests spätestens dann erfolgreich durchlaufen, wenn Code ins Repository eingechecked wird. Doch wie ist es um die Qualität der Modultests bestellt? Wer das nicht gewährleistet ist fernab von produktiver und qualitativ hochwertiger Softwareentwicklung.

Doch wie steht es mit der Qualität der Modultests? Die meisten Projekte kennen die Code-Coverage ihrer Modultests und nehmen diese als Messlatte für die Güte der Modultests. Dieses Qualitätskriterium ist allerdings umstritten und auf jeden Fall unzureichend. Eine hohe Code-Coverage kann auch mit Modultests erreicht werden, die gar nichts abprüfen. Ich habe z.B. einmal ein Projekt kennengelernt, wo automatisiert jede Methode der Applikation in einem Test aufgerufen wurde, ohne etwas zu testen. Das Ergebnis: Hohe Coverage, zufriedenes Management, keine Qualität.

Was also macht gute Modultests aus?
  • Gute Modultests laufen automatisiert und fehlerfrei durch - Die Grundvoraussetzung für effektive Modultests. Ist eine dieser beiden Bedingungen nicht gegeben bringen Modultests kaum Qualität und kosten zudem noch mehr Aufwand. Idealerweise schafft man diese Grundvoraussetzung durch die Einführung entsprechender Quality-Gates: Alle Entwickler müssen bevor sie Codeänderungen in das Repository einchecken alle Modultests fehlerfrei durchlaufen lassen und der Build schlägt fehl (ohne ein ausführbares Programm zu produzieren), wenn die Unit-Tests nicht fehlerfrei durchlaufen. 
  • Gute Modultests laufen rasch durch und sind keine Integrationstests - Alle Modultests eines Projektes sollten innerhalb weniger Minuten durchlaufen, ansonsten werden sie oft nicht gestartet. Dies erreicht man am einfachsten indem man sicherstellt, dass Modultests nur einzelne Klassen testen und nicht die Integration mit anderen Klassen/Systemen. Durch geeignetes Mocking an Klassen oder Schichtgrenzen kann das einfach sichergestellt werden. 10.000 Modultests in wenigen Sekunden durchlaufen zu lassen ist dann leicht möglich. Die Einführung einer Quality-Gate, die sicherstellt, dass der Build fehlschlägt, wenn die Modultests länger als 1 Minute laufen, kann hier hilfreich sein.
    Integrationstests (gerne auch mit JUnit) sind zwar auch wichtig, die von ihnen gefundenen Fehler sind allerdings meist aufwändiger zu lokalisieren. Darum sollten zuerst alle Fehler der Modultests gelöst werden, bevor die (übriggebliebenen) Integrationstestfehler angegangen werden. Integrationstests können beim Build parallel zu den Modultests, aber einfach ohne Zeitlimit gestartet werden.
  • Gute Modultests testen den Vertrag und nicht den Algorithmus - Modultests sollen gemäß dem Design-by-contract-Prinzip möglichst nicht die Interna einer Methode testen, sondern nur ihre externen Auswirkungen (wie Ausnahmen, Rückgabewerte, oder Zustandsänderungen). Darum sollten sie idealerweise auch vor der Implementierung der zu testenden Methode entwickelt werden. Mocks sollten niemals dafür verwendet werden, den korrekten Aufruf anderer Methoden zu prüfen, sondern nur um das zu testende Modul für den Test herzurichten und die nach dem Methodenaufruf erwarteten Zustandsänderungen abzuprüfen.
  • Gute Modultests können in beliebiger Reihenfolge laufen und prüfen tatsächlich etwas ab - Jeder Modultest sollte das System so hinterlassen, wie er es vorgefunden hat. Damit ist sichergestellt, dass Fehler nur entweder im getesteten Modul, oder im Test selber liegen können. Darüberhinaus sollte jeder Test Assertions enthalten, ansonsten testet er ja nicht den Contract der getesteten Method, also das was die Methode wirklich ausmacht. Quality-Gates können auch hier hilfreich sein - man könnte z.B. mittels statischer Codeanalyse wie beispielsweise einer PMD Regel sicherstellen, dass alle Modultests Assertions enthalten.
Erst wenn sichergestellt ist, dass man gute Modultests hat, sollte man auf die Code-Coverage der Modultests achten. Testabdeckung ist aber leider eine oft zweifelhafte Metrik für die Qualität der Tests, so erreicht man beispielsweise durch schlechte Modultests oft einfach eine hohe Testabdeckung ohne wirklich etwas für die Qualität der Applikation geleistet zu haben.

Achtet man auf die Testabdeckung, so muss einem klar sein, dass damit nur eine Aussage darüber getroffen werden kann, welche Codezeilen vom Test berührt wurden - nicht welche Codezeilen vom Test getestet wurden. Nur die Codezeilen, welche eine Auswirkung auf die schlussendlich im Test geprüften Assertions haben, werden durch den Test tatsächlich auch getestet.

Was wir benötigen ist eine Weiterentwicklung der Code-Coverage der Tests in Richtung Assertion-Coverage: Assertion-Coverage berechnet wieviel Prozent der Instruktionen/Codezeilen/Verzweigungen auch tatsächlich durch eine Assertion im Test geprüft werden. Dazu müsste bei der Ausführung der Tests nicht mitgelogged werden, welche Instruktionen/Codezeilen/Verzweigungen berührt wurden, sondern welche Attribute sie beeinflussen und welche anderen Methoden sie aufrufen. Bei den Assertions selbst müsste überprüft werden, welche Attribute oder Returnwerte sie prüfen. Mit diesen Daten könnte man darauf schliessen, welche Instruktionen/Codezeilen/Verzweigungen durch die Assertions der Modultests direkt oder indirekt geprüft werden.

Wenn jemand dieses Thema (z.B. für Diplomarbeit / Dissertation / Toolentwicklung / ...) aufnehmen möchte - ich helfe gerne...
web analytics