p.blog
Unternehmenskultur, Softwareentwicklung und Architektur

9. Juni 2020

Are you ready to think outside the box?

}

7 Minuten Lesedauer

Inside the out … äh … outside the in?

Dass es unterschiedliche, teils widersprüchliche Sichtweisen auf automatisierte Software-Tests gibt, haben wir schon beleuchtet. Das der Blick auf White- und Black-Box-Tests mindestens genauso ambivalent sein kann, überrascht vielleicht doch. Und zeigt, dass die Sicht auf White/Black-Box-Tests stark vom Kontext abhängt. Wir haben uns dafür entschieden, die Realität nicht weiter zu verbiegen, sondern dies zu akzeptieren und uns auf den resultierenden Teufelskreis einzulassen. Also haben wir White- und Black-Box-Tests aus zwei Blickwinkeln beleuchtet, letztere natürlich mit Schwarzlicht. Viel Spaß beim Lesen von Are you ready to think outside the box? und Testing in Boxes.

 

 

Bei pentacor entwickeln wir für unterschiedlichste Kunden Softwaremodule, um ihre Geschäftsprozesse zu unterstützen und neue Möglichkeiten für durchgängige Digitalisierung zu erschließen. Die Implementierungen erstrecken sich dabei von einzelnen Diensten bis hin zu komplexeren Systemen bestehend aus mehreren Services und Single-Page-Applikationen in Kombination mit Lösungen zur Datenhaltung. Und all das ist dann noch eingebettet in die bestehenden, mal mehr, mal weniger komplexen Umgebungen unserer Kunden. Dabei haben wir es uns zur Aufgabe gemacht, jede unserer Softwarekomponenten automatisiert zu testen (einen Ausflug zur Testautomatisierung hat Ramon schon in seinem Blogeintrag Test-Ziele – Warum wir automatisiert testen veröffentlicht). Hier ist unser Ziel, eine qualitativ hochwertige und auch möglichst angemessene Testabdeckung zu erreichen.

Tests für unsere Lösungen lassen sich in zwei Kategorien unterteilen: White-Box-Tests und Black-Box-Tests. Doch wo liegen dabei die Unterschiede? Welche Tests gehören in die jeweilige Kategorie? Und welche Herausforderungen müssen wir in unseren Projekten bei der Umsetzung meistern? Genau das möchte ich gern in diesem Blog mit euch teilen. Also: „Are you ready to think outside the (white and black) box“? Ja? Dann lasst uns loslegen.

 

Black-Box-Tests

Mit Black-Box-Tests überprüfen wir typischerweise komplette Anwendungen. Das kann ein System aus mehreren Diensten oder auch einzelne Dienste sein. Dabei ist es nicht notwendig, die innere Struktur – also die Implementierung – zu kennen. Bei unseren Black-Box-Tests prüfen wir, ob die umgesetzte Anwendung entsprechend ihrer Spezifikation arbeitet. Diese umfasst zum einen fachliche Anforderungen oder User Stories und zum anderen auch technische Aspekte wie die Konformität gegen eine API-Spezifikation (OpenAPI 3.0). Darüber hinaus können wir durch diese Methode sehr gut aufdecken, ob sich die Entwickler der verschiedenen Dienste eines Systems richtig abgestimmt oder aneinander vorbei implementiert haben. Durch das Arbeiten in kleinen, dezentralen Teams wird das durchaus begünstigt und sollte deshalb beim Testen auch nicht außer Acht gelassen werden.

Weihnachten – Foto erstellt von Valeria Aksakova – de.freepik.com

 

Lasst uns als Beispiel annehmen, wir hätten eine Softwarekomponente in Spring Boot mit einer REST-API und eine dazugehörige Single-Page-Applikation (SPA) in React bereitgestellt. Weiterhin kommuniziert die Softwarekomponente per REST noch mit anderen Systemen, die schon in der IT-Landschaft des Kunden existieren. Hier würden wir im Black-Box-Test zunächst unsere beiden Komponenten (das Backend und die SPA) getrennt voneinander testen. Dies wären dann API-Smoke-Tests gegen das Backend, um zu prüfen, ob die API überhaupt im Ansatz läuft und entsprechend der API-Spezifikation antwortet. Als nächste Stufe erstellen wir Systemintegrationstests, die andere bestehende, zu Testzwecken bereitgestellte Dienste oder auch Mocks dieser Dienste verwenden. Die UI der SPA ist dann über End-To-End-Tests mit gemocktem Backend oder auch das vollständige bereitgestellte System validierbar. Somit können wir prüfen, ob beide Komponenten entsprechend ihrer Spezifikationen funktionieren, aber auch ob sich das gesamte System inklusive der bestehenden Dienste im Zusammenspiel resistent und erwartet verhält.

 

Herausforderungen

Das klingt erstmal alles ganz toll, aber leider gibt es bei den Black-Box-Tests auch einige Herausforderungen, die oft nicht so leicht lösbar sind. Dies beginnt schon mit der Integration in vorhandene Authentifizierungs- und Autorisierungsmechanismen. Häufig nutzen SPAs heutzutage OAuth-Flows, aber selten gibt es dafür auch Testsysteme. Oft sind wir gezwungen diese Mechanismen nachzubauen oder wir können uns über umständliche Konfiguration wenigstens einen Testflow anlegen lassen. Meistens benötigen wir auch Testdaten, die für alle Systeme gültig sind. Wenn wir an unser Beispiel denken, dann könnte unsere UI ein Token an das Backend schicken, das wiederum Nutzerdaten (z.B. eine User UUID) bei einem Authentifizierungsdienst anhand des Tokens anfragt. Mit dieser User ID können dann aus den anderen bestehenden Diensten nutzerrelevante Daten gezogen und aggregiert wieder an die UI geliefert werden. Wie schaffen wir es dann, dass für unsere Tests die User ID überall bekannt ist und auch entsprechende Testdaten zurückgibt? Auch hier gestaltet sich die Realität oft sehr kompliziert: Wenn wir Glück haben, können sehr aufwändig entsprechende Daten bereitgestellt werden, die auch dann meist nur einen dedizierten Testfall abdecken, oder, wenn wir Pech haben, können wir diese Tests nur über eigene implementierte Mocks ermöglichen.

 

Vor- und Nachteile

Fassen wir also nochmal die Vor- und Nachteile der Black-Box-Tests zusammen:

Mit dieser Methode verifizieren wir, dass das implementierte System sich entsprechend seiner Spezifikation verhält. Das komplette Zusammenwirken aller implementierten Komponenten des Systems sowie deren angebundene externe Systeme werden getestet und mit der Spezifikation verglichen.

Dabei wird auch sofort klar, ob sich das System widerstandsfähig in erwarteten und unerwarteten Fehlersituationen verhält. Die Tests können auch von Kollegen ohne tiefere Kenntnis über die Implementierung der Dienste erstellt werden.

Das komplette Test-Setup für das automatisierte Testen ist sehr aufwändig. Je nachdem, was die Tests (Testdaten, Testsysteme, eigene Mocks, etc.) benötigen, kann die Testkonfiguration sowie die Aufbereitung sehr komplex und zeitaufwändig werden.

Für End-To-End-Tests mit UI Integration ist es auch wichtig Headless-BrowserFunktionalität bereitzustellen, um eine Testautomatisierung zu ermöglichen. Hier gibt es zum Glück schon einige sehr gute Bibliotheken, die dabei unterstützen können, wie zum Beispiel protractor oder puppeteer. So schön es ist, den kompletten Ablauf zu testen, werden doch hier keine Missstände im Quellcode oder falsche fachliche Implementierungen aufgedeckt. Diese Fragestellungen decken aber die White-Box-Tests ab.

 

White-Box-Tests

Mit White-Box-Tests überprüfen wir genau das, was wir bei einer Black Box außer Acht lassen: die internen Strukturen und Funktionalitäten der eigenen, bereitgestellten Dienste. Dafür brauchen wir eine interne Sicht auf das System. Was bedeutet, dass der Code und der Test in einer engen Beziehung zueinander stehen und wir beide verstehen müssen. Nichtsdestotrotz testen wir nicht nur technische und programmatische Abläufe (wie zum Beispiel die Behandlung von Ausnahmefehlern) sondern auch fachliche Aspekte. In unserem Beispiel könnte das ein Test sein, der prüft, ob wir erhaltene Daten im Backend korrekt aggregieren oder im Fehlerfall eine sinnvolle Rückmeldung an unsere UI zurückgeben.

Broschüre – Foto erstellt von lifeforstock – de.freepik.com

 

White-Box-Tests können in unterschiedlicher Granularität getestet werden. Wir bei pentacor unterscheiden zwischen Tests, die einzelne Einheiten, Methoden oder Klassen prüfen – sogenannte Unit-Tests – und Tests, die Pfade und Funktionalitäten zwischen Einheiten oder Subsystemen testen – sogenannte Module-Tests. Wenn wir hier an unser Beispiel aus dem Black-Box-Test denken, dann wäre ein klassischer White-Box-Test für das Backend zum Beispiel die Prüfung einer Aggregationskomponente, die Daten aus mehreren Quellen zu einem Objekt zusammenfasst. Diese Prüfung können wir dann auf Unit-Ebene durchführen, indem wir die Aggregationseinheit mit Testdaten aufrufen und das Rückgabeobjekt auswerten. Und als nächste Stufe würden wir den Test auf Modulebene ausführen, indem wir die REST-Anfrage an das Backend vortäuschen und das Ergebnisobjekt mit den aggregierten Daten überprüfen. Dabei durchläuft der Test nicht nur die Aggregationseinheit, sondern auch andere benötigte Komponenten zur Ausführung der REST-Anfrage und zur Erstellung des Ergebnisobjekts.

Ein Beispiel für White-Box-Tests der UI ist die Prüfung einer Suchmaske. Auf Unit-Ebene würden wir hier zum Beispiel die verschiedenen Eingaben prüfen und validieren. Dazu gehören Prüfungen wie: „Sind alle erforderlichen Eingaben gemacht?“ oder „Entsprechen die Suchmuster ihrer Spezifikation?“ Aber auch Validierungen der Darstellung einzelner UI-Elemente werden durchgeführt. Das können unter anderem Tests sein, die prüfen, ob alle Labels im korrekten Stil und ihr Text in der richtigen Sprache dargestellt werden.

 

Herausforderungen

Auf den ersten Blick scheinen White-Box-Tests eigentlich relativ klar und einfach. Das ist leider nicht so, denn auch hier sind wir regelmäßig mit Herausforderungen konfrontiert. Das beginnt oft damit, dass wir auch für diese Tests sinnvolle Testdaten benötigen. Diese müssen wir dann entweder selbst aus Spezifikationen der anderen Systeme bauen oder wir erhalten bestenfalls die Daten aus dedizierten Testsystemen.

Die nächsten Fragen, die wir uns dann stellen, sind: „Wie definieren wir die Grenzen unseres Tests?“, „Wo fängt eine sinnvolle, funktionale Trennung unserer Implementierung an und wo hört sie wieder auf?“ Vor allem bei den Module-Tests ist eine Antwort darauf nicht so trivial. Hat unsere Anwendung nur eine simple UI oder einen Microservice als Backend, können wir relativ schnell Abgrenzungen definieren. Werden die Dienste allerdings komplexer, dann ist eine Entscheidung für die Abgrenzung schwerer. Wir machen das bei uns dann häufig zur Teamaufgabe und definieren gemeinsam, wie wir die Testgrenzen strukturieren wollen.

Aber auch das Testen der Fehlerfälle ist herausfordernd. Neben den eigenen, programmierten Exceptions und deren Behandlung, müssen auch noch alle möglichen Fehlerfälle aus angrenzenden Systemen in unseren Komponenten geprüft werden. Wir müssen uns also nicht nur mit dem eigenen Code auseinandersetzen, sondern auch mit den anderen Systemen und deren Verhalten.

 

Vor- und Nachteile

Was sind also nochmal Vor- und Nachteile der White-Box-Tests?

Es werden kleinere, abgetrennte Funktionalitäten geprüft und nicht ein komplettes System. Die internen Codestrukturen und Fehlerbehandlungen werden intensiv geprüft. Aber auch fachliche und technische Abfolgen zwischen Komponenten werden validiert. Die White-Box-Tests können alle während der Build-Zeit des Codes laufen, benötigen keine zusätzlichen Mocks oder Datenbanken und sind daher sehr einfach zu automatisieren.

Die Abgrenzung der Tests und die Aufbereitung von Testdaten können je nach Komplexität eines Dienstes sehr aufwändig sein. Wir können uns nicht nur mit dem Code beschäftigen, wir müssen auch die angrenzenden Systeme überschauen. Die komplette Funktionsweise unseres Dienstes in seiner eingebetteten Landschaft wird mit diesen Tests außer Acht gelassen. Diese Betrachtung wird über die Black-Box-Tests abgebildet.

 

Schlusswort

Beide Methoden, die wir Pentacornesen in unseren Systemen verwenden, haben ihre Vorteile und Nachteile. Allerdings gelten die Nachteile für uns nicht als Ausrede, diese Tests dann nicht zu implementieren. Ganz im Gegenteil – wir stellen uns der Herausforderung und finden eine Lösung, die zum einen für unseren Kunden aus zeitlicher und finanzieller Sicht sinnvoll ist und zum anderen uns nachts ruhig schlafen lässt. 🙂  

Also stellt euch der Herausforderung: Be ready to think outside the box!


Bildquellen:
Daumen hoch: OpenClipart-Vectors auf Pixabay
Daumen runter: OpenClipart-Vectors auf Pixabay

Teile diesen Beitrag