Unit- vs. Integration-Tests

Testarten

Beim Testing von objektorientierter Software unterscheidet man zwischen verschiedenen Arten von Tests. Die Unterscheidung kann in beliebig detailliert ausfallen, sehr häufig jedoch wird die Unterteilung danach gerichtet, wie gross die Teile der gesamten Applikation sind, die mit jeweils einem Test getestet werden. 

 

Eine einfache Kategorisierung ist diejenige in Unit-, Integration- und System-Tests. Dabei werden beim Unit-Test einzelne, isolierte Klassen oder ähnlich kleine Codeanteile getestet. Beim Integration-Test wird dann das Zusammenspiel mehrerer Klassen geprüft, und beim System-Test schliesslich die gesamte Applikation, oder grosse Teile davon.

Unit-Tests

Beim Unit-Test werden die Funktionen der kleinstmöglichen noch sinnvollen Code-Einheiten geprüft. In der Regel finden diese Tests auf Ebene der Klasse statt. Einzelne Unit-Tests sind meist dazu eingerichtet, das Verhalten einer Methode in einem ganz bestimmten Fall zu testen. Mehrere Unit-Tests werden dazu verwendet, um möglichst viele Fälle einer Methode abzudecken, indem die Parameter variiert werden. Wenn dies für alle Methoden einer Klasse gemacht wird, ist die Klasse komplett getestet.

 

Wichtig beim Unit-Test ist, dass die Klasse isoliert getestet wird. Das bedeutet, dass alle Interaktionen (z.B. Methodenaufrufe) mit anderen Klassen durch sogenannte Mocks ersetzt werden. Mocks sind Objekte, welche anstelle von "echten" Instanzen der Klassen verwendet werden. Mocks haben keine Programmlogik, und für alle Aufrufe von Funktionen der gemockten Objekte wird genau vordefiniert, welche Werte zurückgegeben werden.

 

Die meisten Mocking- bzw. Unit-Testing Frameworks unterstützen nur das Mocken von Interfaces, zum Teil auch abstrakten Klassen, jedoch selten das Mocken von konkreten Klassen. Da viele der Applikation, die ausführlich mit Unit-Tests getestet werden auch nach dem Clean Code-Prinzip der Interface Segregation enwickelt werden, ist diese Einschränkung jedoch kaum ein Problem. Für andere Fälle gibt es zum Teil auch wenige Frameworks, welche das Mocken von konkreten Klassen ermöglichen (siehe Beispiel unten).

 

Unit-Tests werden in der Regel in einem von der zu testenden Applikation separat gehaltenen Projekt organisiert. Meist wird für jede zu testende Klasse eine Klasse erstellt, welche mit dem Namen der zu testenden Klasse benannt wird, gefolgt von "Test". Jede Methode in dieser Klasse entspricht einem Test, oder wird für das Setup von komplexeren Tests verwendet. Die Test-Methoden erhalten in der Regel lange Namen, welche beinhalten was getestet wird, welcher Fall abgehandelt wird, und was für ein Resultat erwartet wird.

Integration-Tests

Diese Art von Tests dient dazu, das Zusammenspiel zwischen mehreren Klassen zu testen. Auch hier wird pro Test ein ganz bestimmtes Szenario getestet, einzig mit dem Unterschied, dass sich die Ausführung über mehrere Methoden und Klassen erstreckt.

 

Um diese Tests durchzuführen kann auf die selben Frameworks zurückgegriffen werden, wie bei den Unit-Tests. Mocking ist bei Integration-Tests jedoch unüblich.

 

Was bei den Integration-Tests jedoch häufig vonnöten ist, ist das Aufsetzen komplexerer Szenarien. Im Gegensatz zu Unit-Tests, welche jeweils nur sehr kleine Teile der gesamten Applikation umfassen, decken Integration-Tests zwar grössere Codeteile ab, aber diese sind immer noch klein im Vergleich zum ganzen System.

Beispielprojekt

Als Beispiel-Projekt dient eine Reinkarnation des Spiels "Zork" von 1982, umgesetzt mit Java. Der gesamte Quellcode kann hier gefunden werden: https://github.com/Finrod-Amandil/Zork.

 

Die Software ist kurz gesagt wie folgt aufgebaut: Die Spieldaten werden in einem grossen Spektrum von Klassen gehalten, zum Beispiel die Klasse "Item" und deren Unterklassen. Weitere Klassen dienen zur Verwaltung dieser Daten, wie etwa die Klasse "ItemCollection". Praktisch die gesamte Spiellogik ist in den Unterklassen der Klasse "Command" versteckt. Jede dieser Klassen entspricht einer möglich Aktion im Spiel, wie etwa das Wechseln des Standortes oder das Aufnehmen, Ablegen, Bewegen oder Benutzen von Items.

Beispiel Unit-Test (ohne Mocking)

Ein erstes einfaches Beispiel von Unit Tests lässt sich anhand der Test für die Klasse Clock aufzeigen. Die Klasse Clock behält über die Spielzeit den Überblick, welche sich beim Ausführen von Commands ändert. Da sie auf keine anderen Klassen zurückgreifen muss, ist es sehr einfach, diese Klasse mit Unit Tests zu testen, da keine Objekte gemockt werden müssen. Als Framework für das Unit-Testing wird hier das weit verbreitete JUnit 4 verwendet.

Beispiel Unit-Test (mit Mocking)

In diesem zweiten Beispiel wird die Klasse Exit getestet, welche von einigen anderen Klassen abhängt. Ein Exit-Objekt verbindet zwei Location-Objekte, welche wiederum den einzelnen Räumen und Gebieten der Spielwelt entsprechen.

 

Hier wurde nicht mit Interface Segregation gearbeitet, das heisst die Abhängigkeiten sind alle konkrete Klassen. Mithilfe des Frameworks JMock und dessen Erweiterung ClassImposteriser (Mehr Infos) ist es jedoch trotzdem möglich, diese Klassen zu mocken.

Beispiel Integration-Test

Wie bereits erwähnt ist der grösste Teil der Spiellogik in den Command-Klassen untergebracht. Es bietet sich also an, diese Klassen zu testen, um die Gesamtfunktionalität der Anwendung zu prüfen. Einer der am spannendsten zu testenden Aspekte der Klassen ist jedoch die Interaktion mit den anderen Klassen. Somit werden diese nicht gemockt sondern "echte" Objekte so vorbereitet, dass ein ganz bestimmtes Szenario entsteht. Da nun also mehr als eine Klasse im Spiel ist, ist der Test kein Unit-Test mehr, sondern ein Integration-Test.

 

Konkret wird hier die Klasse CommandTake getestet, welche dem Befehl TAKE entspricht. Dieser dient dazu, ein Item, welches sich in einem Raum befindet, in das Inventar des Spielers aufzunehmen.