testing

Selenium

Es gibt verschiedene Test-Technologien, die auf unterschiedlichen Ebenen einer Applikation zum Einsatz kommen. Während JUnit, TestNG und Mockito üblicherweise nicht sichtbare und meistens relativ kleine Teile der Applikation testen, braucht es auch Testframeworks für umfassende Tests, wie Integrationtests und User Acceptance Tests (UATs). Hier bieten sich Arquillian und Selenium an. Während Arquillian ziemlich schwergewichtig unterwegs ist, ist Selenium das Tool der Wahl, wenn es um's automatisierte Testen von WebGUIs geht.

Selenium-Tests können auf zwei Arten erstellt werden:
Zum Einen gibt es die Möglichkeit mit einem der verfügbaren Firefox-Plugins (z.B. "Selenium Builder") Tests zu erstellen. Dabei startet man seine zu testende Web-Applikation und startet das Plugin, welches nun die Interaktionen mit der Web-Applikation im JSON-Format aufzeichnet. Ein so aufgezeichneter Test lässt sich über den Selenium Builder laden und starten. Für eine Integration in einen nightly build oder sonstige automatisierte Tests ist dies kein gangbarer Weg. Ausserdem wird es in komplexen Fällen schwierig das generierte JSON-File zu bewirtschaften.
Der andere Weg ist der programmatische Ansatz. Hierbei werden JUnit bzw TestNG-ähnliche Tests geschrieben, die sich problemlos in Jenkins bzw. Hudson im Rahmen eines nightly builds aufrufen lassen. Diese Variante ist generell vorzuziehen, da oft eine Automatisierung der Tests gefordert ist.
 

Mockito: Lessons Learned

Ich möchte hier einige der Dinge sammeln, die mir bei der Arbeit mit Mockito immer wieder passieren oder Details, die ich mir merken möchte. Vielleicht hilft's ja auch Anderen ...

Typen-Definition bei der Deklaration von Methoden-Aufrufen

Beispielsweise bei verify() und when() muss angegeben werden, welche Parametertypen übergeben werden. Dies braucht Mockito, um die Methoden-Signatur zu erkennen. Ausserdem kann man statt einem Typ auch direkt einen Wert mitgeben. Dies ist zum Beispiel sinnvoll, wenn man einen Wert beim Aufruf der zu testenden Methode mitgibt, der unverändert an die gemockte, tieferliegende Schicht übergeben werden soll.

Beispiel 1:

Mockito.verify(fsDaoMock).setStatus(1L, 1L, Status.ACTIVE, CallerToken.USER);

Hier verwende ich konkrete Werte, um zu prüfen, ob genau dieser Aufruf gemacht wurde.

Beispiel 2:

Mockito.verify(fsDaoMock).setStatus(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(Status.class), Mockito.any(CallerToken.class));

Hier verwende ich nur Typen und definiere jeweils keine Werte.

Wichtig: Wenn ich diese beiden Ansätze mischen will (also zum Teil konkrete Werte und beim Rest 'nur' Typen definieren), muss ich das so machen:

Mockito.verify(fsDaoMock).setStatus(Mockito.anyLong(), Mockito.anyLong(), Mockito.eq(Status.ACTIVE), Mockito.eq(CallerToken.USER));

Alles andere führt zu Laufzeit-Fehlern.

Mocking eines privaten Attributes unter Spring

Ich möchte eine Klasse testen, die über Dependency Injection (@AutoWired) ein DAO bekommt. Natürlich sollen die DAO-Methoden nicht aufgerufen werden.

Die Deklaration des DAO ist folgendermassen:

@Autowired private FileStoreDao fsDao;

Es wäre aus Architekturüberlegungen inakzeptabel direkten Zugriff auf fsDao zu gewähren (z.B. getter oder Aufweichen des Zugriffsmodifiers). Spring hat eine Util-Klasse, die in diesem Fall weiterhilft:

FileStoreServiceImpl fileStoreServiceImpl = new FileStoreServiceImpl(); FileStoreDao fileStoreDaoMock = Mockito.mock(FileStoreDao.class); ReflectionTestUtils.setField(fileStoreServiceImpl, "fsDao", fileStoreDaoMock);

Testing-Support in Maven 3

Grundsätzlich unterscheidet man ja zwischen Unit-Tests und Integrations-Tests. Unit-Test testen das Verhalten einzelner Klassen, während Integrations-Tests das Zusammenspiel mehrerer Klassen testen. Üblicherweise (bzw. richtigerweise) sind Unit-Tests schnell und werden bei jedem Build durchgeführt und Integrations-Tests laufen länger und werden nur zu "speziellen" Gelegenheiten (also beispielsweise in nightly builds) durchlaufen. In Maven gibt es Support für diesen Unterschied unter Verwendung des failsafe-Plugins:

<dependency>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.8.1</version>
    <scope>test</scope>
</dependency>

Zur Unterscheidung zwischen den beiden Test-Arten wird der Klassenname herangezogen: UnitTest-Klassen enden in *Test und Integrationstests in *IT. Bei einem Clean and Build in NetBeans (entspricht  mvn install) werden alle UnitTests durchgeführt. Durch mvn failsafe:integration-tests (bzw. einen entsprechenden Menupunkt, der sich in NetBeans einfach erzeugen lässt) werden die Integrationstests durchgeführt.

Testen mit TestNG und Mockito

Die Frage beim Schreiben von Testklassen ist ja, wie sicher gestellt wird, dass eine Applikation umfassend mit Tests abgedeckt ist und mit welchen Mitteln dies getan und überprüft wird. Für TestNG gibt es viele Tutorials und auch konzeptionell birgt es wenig Überraschungen. Mockito andererseits ist mässig dokumentiert und bringt einen komplett anderen Ansatz mit. Deswegen möchte ich hier meine Erkenntnisse zu Mockito zusammenstellen:

Integration von Mockito in Testklassen

Ich bin eigentlich kategorisch gegen statische Imports, da sie den Code unleserlich machen und mit dem Support heutiger IDEs unnötig sind. Bei Testklassen bin ich allerdings zum Schluss gekommen, dass static Imports der Klassen aus dem Testframework zu kompakterem Code führen können und durch die Isolation der Testklassen klar ist, woher die entsprechenden Methoden kommen.

Wann schreibe ich welche Art von Tests?

Bei Klassen, die auf anderen selbstgeschriebenen Klassen basieren, verwende ich primär Mockito um das Verhalten einer Klasse innerhalb ihrem Kontext zu beschreiben und zu testen. Dabei ist es wichtig nur die direkt referenzierten Klassen zu mocken. Für Tests der Importparameter ist TestNG gut geeignet.

Kurz: das Verhalten eines Subsystems oder Methoden mit komplexen Abläufen (if, switch, ...) werden mit Mockito getestet; für alle anderen Tests gibt es TestNG.

Verwendung von Mockito

Mit Mockito lassen sich Klassen und Interfaces mocken (Ausnahme sind statische Klassen und final-deklarierte Klassen). Da es, wie bereits gesagt, kein gute Dokumentation zu Mockito gibt (wer etwas kennt, soll es mir doch bitte zeigen), stelle ich in verschiedenen Artikeln meine Erfahrungen und Erkenntnisse zusammen.

JavaDoc zur Version 1.8.5 ist hier.

Ich fange mit der Klasse Mockito, bzw der Annotation @Mock an:

Beide Varianten dienen dazu eine bestehende Klasse oder ein Interface zu mocken, d.h. mit einer temporären Implementierung zu versehen, die nur während der Gültigkeit des Objekts in Tests ansprechbar ist und deren Methoden nichts tun. In Beispielen sieht man häufig, dass bestehende Java-Klassen gemockt werden. Das ist natürlich nicht praxisnah; normalerweise mockt man eigene Klassen.

Bevor ich auf die >20 statischen Methoden dieser zentralen Klasse in einem separatem Artikel eingehe, möchte ich kurz verwandte Klassen positionieren.

Die Klasse BDDMockito ist eine Variation von Mockito. Diese unterstützt Behaviour getriebene Entwicklung; damit ist gemeint, dass die Klasse den Use-Case des Kunden präzis und nahe seiner Sprache abbildet. Beim Durchführen der Tests wird geprüft, ob das gewünschte Verhalten abgebildet wird.

Die Annotation @Captor scheint für einen bestimmten Testcase ausgelegt zu sein, den ich aber noch nicht verstanden habe.

Eine weitere Annotation in diesem Zusammenhang ist @Spy. Dies lässt sich auch mit Mockito.spy(...) abbilden. Diese Funktionalität ermöglicht es ledigliche Teile von Klassen zu mocken, während andere 'normal' laufen. Bei Klassen, die nur statische Methoden enthalten, kann das funktionieren. Sinn macht es wohl nur in sehr sepezielle Fällen. Besser ist's wohl @Spy zu vergessen.