16. Apr 2018
Lesedauer 7 Min.
Der Integrationstest
Softwaretest, Teil 5
Wann ist er sinnvoll, was gilt es zu beachten und wie ist er am besten umzusetzen?

Die letzten vier Teile von Davids Deep Dive haben die Grundlagen des Testens erklärt und Systemtest und Unit-Test genauer unter die Lupe genommen. Dabei hat sich gezeigt, dass jede Testart ihre Vor- und Nachteile hat und dass neben der Softwarearchitektur und dem Kenntnisstand der Entwickler vor allem die Faktoren Geld und Ressourcen eine zentrale Rolle bei der Wahl der Testarten spielen.Nun geht es um die letzte große Testart: den Integrationstest. Während sich System- und Unit-Test relativ stark voneinander abgrenzen lassen, ist dies beim Integrationstest nicht mehr so einfach, da er in Vielem dem Unit-Test sehr ähnelt und die Unterscheidung der beiden Arten sich schlussendlich nur aus der Sichtweise ergibt.
Der Nachteil der Unit-Tests
Wie in der letzten Ausgabe beschrieben, eignen sich Unit-Tests hervorragend dazu, einzelne voneinander isolierbare Funktionsblöcke (Methoden, Klassen, Komponenten etc.) auf Funktionssicherheit hin zu überprüfen [1]. Der große Vorteil dieser Testart ist aber auch gleichzeitig ihr größter Nachteil: Denn nur weil zwei Funktionsblöcke unabhängig voneinander tadellos funktionieren, muss dies nicht zwangsläufig auch für deren Zusammenspiel gelten. Ein in Online-Foren oft zitiertes Beispiel zeigt das Problem wohl am besten: Eine Türklinke nebst Schloss mag für sich genommen einwandfrei funktionieren; wird sie jedoch in eine Schiebetür eingesetzt, so ist die Gesamtfunktionalität sinnlos und falsch. Genau dieses Problem gehen Integrationstests an.Der Integrationskontext
Während bei einem Unit-Test eine einzelne Einheit aus dem System herausgelöst und in einem isolierten Testkontext getestet wird, definiert ein Integrationstest innerhalb des Systems zunächst den sogenannten Integrationskontext, wie in Bild 1 zu sehen ist. Dieser umschließt alle Subsysteme, die im Zusammenspiel – „integriert“ – getestet werden sollen.
Im Beispiel von Bild 1 sind dies also die Subsysteme D, E und F. Eventuelle Abhängigkeiten aus diesem Systemkontext heraus, also in dem Beispiel die Abhängigkeiten D von C, D von G und F von H, müssen durch Attrappen ersetzt werden. Dabei werden vergleichbare Techniken wie bei den Unit-Tests verwendet. Die entsprechenden Systeme/Subsysteme innerhalb dieses Integrationskontextes werden nun durch die manuelle oder automatische Ausführung von verschiedenen Testfällen auf die Sicherheit der gemeinsamen Funktionalität getestet.
Integrationstests sind Unit-Tests
Die Unterscheidung zwischen Unit- und Integrationstest ist nicht immer so eindeutig, wie der vorangegangene Abschnitt sie erklärt hat, denn zur Definition muss auch immer die Architekturebene genannt werden, von der aus das Ganze betrachtet wird.Bild 2 zeigt die Modularisierung einer Beispielanwendung. Wird die Klasse K1 isoliert getestet, so handelt es sich um einen Unit-Test auf Klassenebene. Gleichzeitig werden bei diesem Test aber auch alle Methoden (M1 bis M3) in Integration, also zusammen getestet. Was also ein Unit-Test auf Klassenebene ist, ist gleichzeitig auch ein Integrationstest auf der Methodenebene.
Ebenso verhält es sich, wenn die Klassen K1 und K2 integriert getestet werden: Dann handelt es sich um einen Unit-Test von C1 auf Komponentenebene. In beiden Beispielen werden immer alle Subsysteme eines Systems in Integration getestet, in diesem Fall spricht man auch von einem „Vollintegrationstest“.In den Beispielen entspricht der Integrationskontext immer einem System auf nächsthöherer Ebene; das muss aber nicht immer so sein. So könnten sich die Klassen K1 und K3 aus Bild 3 in einem Integrationskontext befinden. Auch wenn dieser auf nächsthöherer Ebene nicht durch ein reales Element umgesetzt wird, so kann es als logisches Element verstanden werden, hier würde es IC1 heißen. Unabhängig davon, ob es auf der höheren Ebene ein reales oder logisches Element ist: Der Integrationstest ist hier ein ganz normaler Unit-Test und damit gelten auch alle Überlegungen – und besonders die Vor- und Nachteile – der letzten Episode von Davids Deep Dive zum Thema Unit-Tests [1] auch für diesen Integrationstest.
Die Konsequenz
Somit wird die wichtigste Voraussetzung für Integrationstests klar: Wie bei Unit-Tests müssen die architektonischen Voraussetzungen gegeben sein, um den Integrationskontext aus dem System herauslösen und ihn anschließend unabhängig vom restlichen System testen zu können.Wie bei den Unit-Tests ist dies oft nicht der Fall, sondern es sind teure Refaktorisierungen nötig, um diese Voraussetzung zu schaffen. Der Unterschied ist, dass dies bei einem Integrationskontext meist weit weniger Aufwand erfordert als für einzelne Klassen. In Bild 4 wurde der Integrationskontext aus Bild 1 mit drei Kontrakten isoliert (IC, IG, IH). Die restlichen Abhängigkeiten zwischen den Subsystemen D, E und F können unverändert direkt mit der Implementierung arbeiten.
Neben der Validierung von zusammenhängenden Funktionalitäten ist also ein großer Vorteil von Integrationstests, dass diese in schlecht entworfenen Systemen mit weniger Zusatzaufwänden ausgeführt werden können.
Die Nachteile
Wie bereits erwähnt, führt die Ähnlichkeit zwischen Integrations- und Unit-Tests dazu, dass auch alle Nachteile der Unit-Tests bei den Integrationstests zum Tragen kommen. Wichtig ist zunächst, dass Integrationstests meist im Quellcode abgebildet werden, also durch Test-Frameworks wie NUnit, JUnit Mocha oder vergleichbare. Das bindet beim Testen teure Entwicklungsressourcen, zum einen für das Erstellen, zum anderen aber auch für die langfristige Pflege der einzelnen Testfälle. Somit sollte bei der Entscheidung für die richtige Testart zuerst beantwortet werden, wie viele Entwicklungsressourcen für das Testen erübrigt werden können.Weiterhin ist zu berücksichtigen, dass ein Integrationstest auf einer Ebene ein Unit-Test auf der nächsthöheren Ebene ist. Im Beispiel von Bild 4 stellt ein Integrationstest die Funktionssicherheit für den Integrationskontext sicher – ob dieser jedoch mit der restlichen Anwendung reibungslos zusammenarbeitet, ist damit nicht garantiert. Dazu müssten weitere Integrationskontexte mit immer größerem Umfang geprüft werden; wenn der Integrationskontext dann irgendwann die gesamten Subsysteme einer Ebene umfasst (in Bild 4 also die Subsysteme A bis H), handelt es sich um einen Vollintegrationstest aller Subsysteme, somit also um einen Unit-Test auf der nächsthöheren Ebene und damit um den Systemtest selbst. Daraus folgt: Wirkliche Funktionssicherheit einer Anwendung kann nur der Systemtest bieten.Wann und wann nicht?
Wann sollten Integrationstests nun angewendet werden und wann sollte man davon lieber Abstand nehmen? Wieder einmal lautet die typische Beraterantwort: Es kommt darauf an. Zunächst ist die Frage, wie viele Ressourcen auf Entwicklerseite freigemacht werden können. Denn, wie schon gesagt, Integrationstests werden meist durch Quellcode umgesetzt und somit muss dafür auch Zeit aufgewendet werden.Wenn die Antwort lautet, dass es keine freien Ressourcen gibt, müssen entweder welche geschaffen oder die Entwicklungsgeschwindigkeit muss reduziert werden – andernfalls kommen Unit- und Integrationstests nicht infrage und ein manueller Systemtest stellt eventuell eine gute Lösung dar.Lassen sich Ressourcen beschaffen, ist es wichtig herauszufinden, was getestet werden muss: Geht es um kleine Funktionalitäten, die meist durch eine einzelne Klasse abgebildet werden, oder geht es um deren Zusammenspiel, also meist komplexe Abläufe oder gar ganze Anwendungsfälle? Für letzteren Fall wären die Integrationstests die richtige Lösung, da sie mehr und vor allem zusammenhängende Funktionalität testen.Sind bei dieser Entscheidung die Unit-Tests die bessere Wahl, sollten Sie noch einen genauen Blick auf die Architektur werfen. Bei dem allgemeinen Verständnis von Unit-Tests handelt es sich um isolierte Tests auf Klassenebene, was voraussetzt, dass auf dieser Ebene auch tatsächlich entkoppelt wurde. Ist dies nicht der Fall, ist der zu erwartende Aufwand für eine entsprechende Refaktorisierung mitunter sehr hoch. Lässt dieser sich nicht leisten, sollten Sie auf Integrationstests zurückgreifen, da der Aufwand, einzelne Integrationskontexte isolationsfähig zu machen, meist wesentlich geringer ist als für jede einzelne Klasse selbst.Fazit
Beim Integrationstest werden mehrere Elemente auf einer Ebene in einem Integrationskontext zusammengefasst und deren Funktionalitäten gemeinsam – in Integration – auf Richtigkeit hin getestet. Das ermöglicht eine höhere Funktionssicherheit als bei Unit-Tests, allerdings erfolgen die entsprechenden Prüfungen nicht gleichermaßen differenziert und weisen auf der nächsthöheren Ebene dieselben Nachteile auf wie die angesprochenen Unit-Tests.Trotzdem: Integrationstests sind meist die bessere, weil günstigere Wahl, wenn es um das Testen in Systemen mit schlechter(er) Architektur geht oder wenn größere Funktionsblöcke zu testen sind, deren Umfang einem Anwendungsfall auf Anwendungsebene nahekommt. Da Integrationstests meist codiert werden, ist zunächst aber die wichtigste Frage: Haben die Entwickler Zeit für die Codierung und Pflege der Testfälle?Die Grundlagen des Testens sind damit geschaffen, die verschiedenen Arten und ihre Vor- und Nachteile kennen Sie nun. Aber sollten Sie tatsächlich diese teure Investition in Tests wagen? Werden sie Ihre Probleme lösen oder nur Geld verbrennen? Oft ist eher Letzteres der Fall und das Ganze eine Schnapsidee. Warum? Das erfahren Sie in der nächsten Episode von Davids Deep Dive.Fussnoten
- David Tielke: Der Unit-Test, Softwaretests Teil 4, dotnetpro 4/2018, Seite xx ff., http://www.dotnetpro.de/A1804DDD