Mit Test Scope ist hier gemeint, was der Umfang dessen ist, was der Test umfasst.
Teste ich also eine Methode, eine Klasse, eine Komponente oder ein Packages?
Meistens wird mir diese Frage anders gestellt, beispielsweise wie folgt:
Für was schreibe ich welche Tests? Einfach nur Unit Tests zu schreiben wird ja nicht ausreichen.
Aber wenn ich jetzt einen Integration Test schreibe, wo fängt der an und wann muss ich einen anderen Test bemühen?
Diese und ähnliche Fragen versucht diese Seite zu beantworten. Sicherlich ist diese Frage bereits auf vielen Seiten beantwortet, aber ich versuche es mal ein wenig „mathematischer“, weil das ja bekanntermaßen beeindruckt und daher vielleicht mehr überzeugt.
Stellen wir uns eine simple Anmelderoutine auf einer Webseite vor.
Der Benutzer ist in der Lage sich zu registrieren, um einen bestimmten Dienst vorzunehmen.
Da sehe ich in vielen Projekten eine mehrschichtige Lösung, beginnend mit einem Webformular, das ein Javascript enthält, um die ersten Validierungen vorzunehmen oder die Usability zu verbessern.
Anschließend wird die Anfrage über einen MVC-Controller an eine Fassade weitergereicht, die die in ein UserDTO verpackten Daten für einen UserService in ein User-Objekt übersetzt bzw. transferiert.
Der UserService selbst bedient sich im besten Fall eines UserDAOs, das die persistenzorientierten Aufgaben übernimmt.
Ein kleines Diagramm soll dieses Beispiel erläutern, damit ich im Folgenden darüber sprechen kann, was wo und wie exzessiv getestet werden muss.
Wir haben also folgende Testfälle:
- Der Benutzer gibt kein Passwort an. (JavaScript – Validator reagiert mit entsprechender Fehlermeldung)
- Der Benutzer füllt zwar das PLZ-Feld aus, aber nicht die Ortsangabe. (Evtl. bestimmt die Fassade durch Benutzung entsprechender LocationServices, welcher Ort zu dieser Adresse passt und füllt das UserDTO entsprechend.)
- Der Benutzer wählt eine Email-Adresse, die bereits im System registriert ist. (Diese übernehme ich unserem Beispiel der UserService).
- Die angegebene Länge des Namens ist zu groß für das zugehörige Datenbankfeld. (Das UserDAO wirft eine entsprechende Fehlermeldung.)
Alle diese kleinen Beispielfälle zeigen, dass es u.U. sein kann, dass man nicht die Validierung im besten Falle nur an einer einzigen Stelle implementiert. Besonders nahe am Anwender sollte die Validierung stattfinden, weil dies entsprechend performanter ist und ein Benutzer nicht „lange“ auf die Rückgabe der Business Services warten muss.
Allerdings kann es sein, dass gewisse Validierungen eben nur in der Serviceschicht durchgeführt werden können, weil entsprechende Business Methoden hierfür gebraucht werden.
Kurzum, dies ist ein typisches Beispiel einer mehrschichtigen Architektur.
Es wird also für jede Schicht mehrere Sonderfälle (neben dem grünen Fall, wo also kein Fehler auftritt) geben, die entsprechend durch Tests abgedeckt sein sollen.
Sagen wir nunmal es sind pro Schicht ca. 3-4 solcher Test Cases, die uns einfallen würden. Mir ist klar, dass es deutlich mehr werden, aber ich will schon an diesem simplen Exempel statuieren, wie schwerwiegend die Unterscheidung zwischen Unit- und Integrationstest ist.
Integrationstest
Ein Integrationstest würde ab einer eingesetzten Schicht, also beispielsweise dem Webformular selbst, alle Komponenten bis zum Abschluss der Prozedur durchlaufen und das entsprechende Ergebnis zurückliefern. In unserem Fall also alle Schichten unserer Architektur bis zum DAO, d.h. 7 Schichten.
Wenn wir nun für jede Schicht ca. 3-4 unterschiedliche Fälle betrachten mögen, so benötigen wir aufgrund der Integrationssicherheit und ~konsistenz alle Kombination dieser Testfälle, also 3^7 – 4^7 = 2187 – 16384. Zugegeben, es wird Kombinationen geben, die sich ausschließen und demzufolge wird sich die Anzahl der Testfälle etwas reduzieren. Kein wirklich entspanntes Szenario.
Wir können unmöglich alle Kombinationen durchtesten, denn würde jeder CI Server beim Build im Falle, dass einer dieser Tests ca. 1s Laufzeit hat, in etwas 4h benötigen und das nur für ein Szenario, nämlich die Benutzerregistrierung. Man will sich die Komplexität für richtige Geschäftsprozesse nicht mal vorstellen.
Also, keine Integrationstests?!
Nein, natürlich braucht es Integrationstests, denn nur diese zeigen, dass alle Komponenten, die evtl. von unterschiedlichen Entwickler gebaut wurden, auch entsprechend zusammenspielen.
Man wird sich also fragen: Reicht für diesen Test nicht eine Handvoll Test Cases?
Und die Antwort ist (wie übrigens immer): It depends.
Unit Test
Ein Unit umfasst den Test einer einzelnen (kleinsten) Einheit, wobei mir persönlich Methoden als kleinste Einheit zu klein sind, also halte ich es mit den alten Griechen und denke Atome sind untteilbar und nehme meine Klassen als Atome an.
Ein Unit Test testet also eine ganze Klasse.
Wer jetzt denkt, in meinem Projekt gibt es aber Klassen, die so groß sind, dass… dem empfehle ich sofort die beiden Bücher:
- Clean Code Development von Robert C. Martin
- Refactoring von Martin Fowler
Nun haben wir ja der mathematischen Einfachheit halber angenommen, dass es 3-4 Fälle (inkl. Sonderfälle) pro Schicht zu testen gibt.
Ein Unit Test einer Komponente testet nun exakt diese Fälle ab, wir benötigen also:
(3-4) + (3-4) + … + (3-4) = 21 – 28 Test Cases.
Das ist schon überschaubarer oder, um es noch genauer zu sagen, machbar.
Zusammenfassung
Mit meinen Unit Tests stelle ich sicher, dass die einzelnen verwendeten atomaren Komponenten, stabil gegenüber normalen Fällen, Fehlerfällen, aber auch Sonderfällen sind und sich entsprechend (Design by contract, Spezifikation, …) verhalten.
Mit meinen Integrationstests stelle ich sicher, dass die Komponenten richtigerweise miteinander kommunizieren und die Ergebnisse der verwendeten Komponenten verstehen und interpretieren können.
So wie die Entwickler der Ariane 5 Raketen sich nach Zusammenbau der Trägerrakete nicht mehr fragen wollen, ob alle verwendeten Materialien die auf das Gesamtsystem zukommende Belastung standhalten, und deshalb NICHT stillschweigend von entsprechenden Unit Tests ausgehen, sondern diese im Falle der Schrauben zum Teil durch diplomierte Schraubenkontrolleure mikroskopisch durchführen lassen.
So kann man in diesen komplexen System auch nicht mehr verlangen – und es entbehrte auch jeglicher Wirtschaftlichkeit – , dass in Integrationstests jede Kombination von Umständen noch berücksichtigt wird.
Wir brauchen also beides richtig miteinander kombiniert.