Das Wissensportal für IT-Professionals. Entdecke die Tiefe und Breite unseres IT-Contents in exklusiven Themenchannels und Magazinmarken.

SIGS DATACOM GmbH

Lindlaustraße 2c, 53842 Troisdorf

Tel: +49 (0)2241/2341-100

kundenservice@sigs-datacom.de

Drei unterschätzte Verfahren der Testautomatisierung

Beim Thema Testautomatisierung fallen dem Fachmann als erstes geskriptete Oberflächentests, Capture & Replay-Techniken und Unittests ein. Generell werden Tests automatisiert, wenn damit gegenüber der manuellen Ausführung Ressourcen gespart werden können. Für die Regression sind automatisierte Tests unverzichtbar: Nach jedem Update müssen möglicherweise neu eingebrachte Fehler gefunden werden – auch in den Bereichen, die gar nicht direkt von der Aktualisierung betroffen sind. Doch Testautomatisierung geht über Capture & Replay und die Ausführung von Unittests für die Geschäftslogik hinaus.
Author Image
Andreas Reif

Author


  • 28.05.2021
  • Lesezeit: 23 Minuten
  • 82 Views

Der Artikel gibt eine kurze Übersicht, beginnend von typischen Automatisierungen, hin zu den Themen, welche das Thema „Automatisierung“ umsetzen, aber nicht unbedingt unter diesem Stichwort eingeordnet werden.

Weitere Felder der Testautomatisierung sind:

  • Aufsetzen von Umgebungen für Tests. Automatische Bereitstellung von Laufzeitumgebungen, Containern und Betriebssystemen.
  • Die Aufbereitung von Testergebnissen.
  • Testfallerstellung: Die IDE hilft bei der Erstellung eines Tests. Bei Model-getriebener Vorgehensweise kann auch das entsprechende Tool bereits Tests auswerfen.
  • ALM (Applicationlifecycle-Management) wird vom Entwickler oft als BackOffice wahrgenommen, nach dem Motto: Irgendwo müssen Aufgaben und Ergebnisse ja gespeichert werden. Ein ALM-Tool ist jedoch vor allem eine Automatisierung von Aufgaben, die der Qualitätskontrolle dienen. Die gesamte Tracability von Änderungen, vom Code mitsamt den zugehörigen Anforderungen wird manuell definiert, aber automatisiert verwaltet.
  • Monitoring von Umgebungen, Application Performance Monitoring (APM). Das können Überwachungsskripte sein, die per Cron-Job angestoßen werden, oder kommerzielle Produkte, welche die Überwachung & Auswertung jeden Tag automatisch übernehmen.

Und es gibt noch einige mehr, hier greife ich drei Ansätze heraus, die meines Erachtens zu wenig berücksichtigt werden.

Drei Ansätze, die der Architektur und dem Projektmanagement guttun

Diese Tests werden zunehmend eingesetzt und haben noch ein großes Potenzial: UI-Schicht im Unittest, Selbsttests und Integrationstests. Die Fragen, die sich hier stellen, sind: Was ist darunter jeweils zu verstehen, wie werden diese Ansätze umgesetzt? Welchen Ort haben sie in der gesamten Teststrategie? Wann werden die Tests geschrieben? Wann werden sie im SW-Zyklus ausgeführt? Welche architektonischen Implikationen sind zu berücksichtigen? Wie sind sie in der üblichen Drei-Schichten-Architektur einzuordnen, siehe Abbildung 1, und wie ist die Abschätzung der Kosten und des Gewinns bei der vorgeschlagenen Vorgehensweise gegenüber dem herkömmlichen Modell?

Abb. 1: Drei-Schichten-Architektur

Allen drei Testverfahren ist gemeinsam, dass sie besser als übliche Unittests auch dazu geeignet sind, nicht-funktionale Eigenschaften zu testen. Also zum Beispiel nicht nur „nach Eingabe aller Daten wird der Senden-Schaltknopf aktiviert“. Sondern auch: „Nach dem Drücken des Senden-Schaltknopfs muss innerhalb 3 Sekunden eine Antwort erfolgen.“ (Bei definierten Rahmenbedingungen wie HW und Auslastung) Oder: „Die Liste muss x Einträge in y Sekunden mit den definierten Datensätzen darstellen.“ Zu den nicht-funktionalen Eigenschaften zählen Antwortzeiten, Speicherverbrauch, Robustheit. Sie werden auch als technische Anforderungen bezeichnet – als Gegenbegriff zu den fachlichen Anforderungen. „Sie haben in der Regel einen Einfluss auf die gesamte Softwarearchitektur.“ [Bal11, S. 109] Je früher Probleme identifiziert werden, desto eher kann die Architektur noch angepasst werden. Wenn ein Datenzugriff ab einer bestimmten Software-Version plötzlich lange braucht, ohne dass dies an der Persistenzschicht selbst liegt, kann man analysieren, ob eine Änderung in der Architektur verantwortlich ist oder die Entwicklung an einen Punkt gelangt ist, ab dem die gewählte Architektur nicht mehr effizient ist.

Die UI-Schicht im Unittest

Die Unittests in der heutigen Form wurden vor über 20 Jahren entwickelt, um gekapselte Logik in Klassen zu testen, wobei die UI damals nicht im Blick war. Doch die UI-Logik, siehe oberes Kästchen in Abbildung 1, ist meines Erachtens ein genauso wichtiger Testkandidat wie alle anderen Logiken. Leider scheitert das isolierte Testen von UI-Logik häufig an technischen Problemen, je nach eingesetztem UI-Framework. Manchmal scheitert es aber auch an dem Entwurf für die Software.

Üblich ist ein Test mit der UI leider nur mit der laufenden Anwendung als Gesamttest. UI-Tests sind daher in der Testpyramide in der Regel oben zu finden, siehe Abbildung 2. Bei den üblichen Unittests wird jedoch zu Recht Wert auf Isolation gelegt. „In der Isolierung kann jedes Objekt schnell getestet werden. Das Ergebnis sind also bessere Entwürfe und schnellere Tests.“, schreibt uns Kent Beck ins Stammbuch. [Bec05, S. 15]

Abb. 2: Testpyramide

Die Aufteilung bei gängigen Architekturen dient diesen Zwecken. So können zumindest Teile der UI-Schicht isoliert getestet werden. Bei einer MVC-Architektur kann der Controller mit einem Unittest getestet werden. Doch die Entwicklung der Architekturen geht weiter. AndroidStudio stellt eine Projektvorlage bereit, die ein ViewModel anlegt, siehe Abbildung 3.

Abb. 3: Android Studio ViewModel in der Projektvorlage

Diese Technik wird seit der Version 3.1 (2018) unterstützt. Das ViewModel soll helfen, die UI-Schicht von der Anbindung an Datenquellen zu entkoppeln. Das Erneuern der UI, etwa beim Drehen des Handys, soll nicht zum Abruf großer Datenmengen führen. [VIEW] Eine Möglichkeit, dies zu verhindern, ist das Zwischenschalten eines ViewModels. Wenn also das Serialisieren in OnSaveInstanceState und das Deserialisieren in OnCreate nicht passend gelöst wird, gibt es die Möglichkeit, das ViewModel die Daten halten zu lassen. Ändern sie sich, so wird über ein Observer-Pattern [GoF94] die UI aktualisiert. Eine Einführung findet sich zum Beispiel hier [ALC17]. Listing 1 enthält als Codebeispiel eine TextView, welche die Anzahl der Einträge der Users-Liste eines ViewModels anzeigt.

public void onCreate( ...
UserViewModel userViewModel =
new ViewModelProvider(this).get(MyViewModel.class);
userViewModel.getUsers().observe(this, users -> {
TextView textViewCount = findViewById(R.id.textCount);
textViewCount.setText(String.valueOf(users.size()))}
);
...
Listing 1: TextView, welche die Anzahl der Einträge der Users-Liste eines ViewModels anzeigt

ViewModel – ein Ansatz zur Aufgliederung der UI-Schicht
Ein ViewModel lässt sich isoliert mit Unittests testen, wenn die Anbindung an die richtige Business-Schicht mitsamt Datenquellen gemockt wird. In der .NET(„dot net“)-Welt ist das MVVM-Pattern weit verbreitet, es steht für Model-View-ViewModel. Es ist ähnlich zu dem gezeigten Vorgehen mit dem ViewModel – besonders, wenn zusätzlich im UI-Code das Binding verwendet wird. Binding ist zum Beispiel, wenn in der UI-Definition das ViewModel abgefragt wird:

android:text=“@{viewmodel.user.name}“

Dann fällt der Aufruf observe(...) in Listing 1 bei OnCreate weg. Nach MVVM soll die UI durchgehend in der ganzen Applikation nur über das ViewModel mit der Business-Schicht und den Daten verbunden sein.

Das ViewModel ist in der .NET-Welt eigenständiger, es wird nicht in einer Methode wie OnCreate befüllt, sondern besorgt sich die Daten selbst. Die großen Vorteile von MVVM sind die isolierte Testbarkeit und die lose Kopplung zwischen UI-Elementen und dem Rest der Applikation. Bei Android wird die Einführung eines View-Models vor allem damit begründet, bei Unterbrechungen der UI die Daten schnell ohne Roundtrips wieder auf die UI zu holen (responsive UI). Das ViewModel hat keine Referenz zu der View (UI-Components) [CHU18], [VIEW]. In dem Punkt sind beide Ansätze von MVVM bei .NET und Android gleich.

MVVM hat jedoch auch Nachteile. Einmal ist die Einführung einer so losen Kopplung zwischen View und ViewModel durchaus gewöhnungsbedürftig. Ein weiterer Nachteil bei Einführung eines ViewModels ist, dass Code, der eine einzige UI-Funktionalität beschreibt, über mehrere Dateien verteilt sein kann, die dann wieder verbunden werden müssen. Das Binding kann bei der Entwicklung durchaus Zeit in Anspruch nehmen. In den Foren spricht von „wiring“, dem Verdrahten. Das ist gegen die Vorteile abzuwägen.

Die oberste UI-Schicht als Software Under Test (SUT) im Unittest
Ein weiterer Bereich, der isoliert getestet werden kann, ist die oberste UI-Schicht, der Code, der die Steuerelemente mit ihren Eigenschaften und Abhängigkeiten definiert. Wenn UI-Elemente von den Anbindungen und der Geschäftslogik genügend getrennt sind, Stichwort „lose Kopplung“, etwa durch ViewModels oder das Injecten der Business-Schichten, können UI-Bereiche isoliert getestet werden. Wenn in der UI die Geschäftslogik referenziert wird, also kein MVVM-Ansatz gewählt wird, muss diese gemockt werden.

Ein einfaches Beispiel anhand eines JPanels: Das Panel beinhaltet drei Fortschrittsbalken. Erst wenn alle auf 100 Prozent stehen, wird ein Button freigegeben. Diese Ansichtslogik wird isoliert getestet, siehe Listing 2. Die Anbindung an die Business-Schicht wird gemockt. Im Beispiel sei der Mock ohne testrelevante Logik, also genau genommen ein Dummy. Die Unterscheidung der verschiedenen Test-Doubles hat Martin Fowler in dem Artikel „Mocks Aren't Stubs“ [FOW07] beschrieben, ein Klassiker. Zur Abgrenzung der UI-Logik von der Geschäftslogik in diesem Beispiel sei bemerkt, dass der Fortschritt, den die Balken anzeigen, eine Sache der UI-Logik ist, also zum Beispiel durch den Anteil der ausgefüllten Eingabefelder bestimmt ist. 

@Test
public void testProgressBarsAtMaximum_NextButtonIsEnabled () {
System.out.println("testProgressBarsAtMaximum_NextButtonIsEnabled ");
// Arrange
JFrame1 frame = new JFrame1(mock(JFrame1Model.class));
assertFalse(frame.jButtonNext.isEnabled());
// Act
frame.jProgressBar1.setValue(frame.jProgressBar1.getMaximum());
frame.jProgressBar2.setValue(frame.jProgressBar2.getMaximum());
frame.jProgressBar3.setValue(frame.jProgressBar3.getMaximum());
// Assert
assertTrue(frame.jButtonNext.isEnabled());
}
Listing 2: Panel mit drei Fortschrittsbalken

Nachteil: Die Steuerelemente müssen für den Test verfügbar sein, also für die Sichtbarkeit den Zugriffsmodifikator „package scope“ (Standard, ohne Schlüsselwort) oder „public“ haben. Bei Standard-Sichtbarkeit ist das Element auch im Test verfügbar. Die IDE hingegen legen die Sichtbarkeit standardmäßig mit private an. Ein Einwand ist, dass bei Erweiterung der Sichtbarkeit das Geheimnisprinzip verletzt würde. Eine Sichtbarkeit erweitern, nur um testen zu können – darf man das? Das Testen ist jedoch ein ganz wesentlicher Bestandteil der SW-Entwicklung. Es ist durchaus erlaubt, dafür etwas Abgeschlossenheit zu opfern. Im Übrigen ist bei jeder Aufteilung von Funktionalitäten, Stichwort „Single Responsibility Principle“ (SRP), erforderlich, die Sichtbarkeit der Schnittstelle zu erweitern. So ist bei einer Vorgehensweise wie MVC erforderlich, dass die Steuerelemente in der View für den Controller sichtbar sein müssen, damit der Controller darauf zugreifen kann. Die Testbarkeit ist meines Erachtens sogar von gleichem Rang wie SRP – daher darf für sie im gleichen Maße das Geheimnisprinzip eingeschränkt werden. Natürlich immer in Abwägung aller Belange und mit dem Ideal, das Geheimnisprinzip durch geschickte Definition der Klassen sogar noch zu verbessern. Alternativ zur Erweiterung der Sichtbarkeit kann vom Unittest per Reflection auf private Member zugegriffen werden, was jedoch die Lesbarkeit des Testcodes erschwert. Die Tests müssen eine Referenz zu den UI-Frameworks haben, siehe die Referenzen in der Abbildung 4 unten rechts.

Abb. 4: UI-Schicht und Referenzen

Ein wichtiges Gebiet der Oberflächen-Logik ist das Validieren von Eingaben. Seit Java 8 gibt es die Möglichkeit, Annotations zu erstellen. Damit können Verstöße gegen Grenzen mit einem Validation-Objekt abgefragt werden, siehe Listing 3. Diese Technik ist Layer-unabhängig, also für Datenzugriff, Geschäftslogik und UI geeignet: „Application layer agnostic validation“ [HIB21]; „Constrain once, validate everywhere“ [BEA21]. Hauptanwendungsfall dürfte jedoch die Eingabevalidierung sein. Die Annotations ergeben neue Möglichkeiten, die Regeln und deren Verwendung in einer UI isoliert zu testen.

public class Customer {
@Size(min = 2, max = 30, message (message =
 "Name ist erforderlich(2 bis 30 Zeichen) ")
private String name;
@Min(value = 16, message = "Alter darf nicht unter 16 Jahren liegen.")
@Max(value = 120, message =
 " Alter darf nicht über 120 Jahren liegen.")
private int age;
...
Listing 3: Annotationen

Eine selbst geschriebene Annotation kann isoliert getestet werden. Aber auch die Anwendung der Annotation im Zusammenhang in der Klasse kann bequem als Unittest abgefragt werden. Das soll kein genereller Ratschlag für Annotations sein, sondern ein Hinweis, welche Wege es gibt, Testbarkeit herzustellen.

In der Testpyramide, siehe Abbildung 2, besteht bei den Unittests, welche in der UI-Schicht isoliert testen, eine Besonderheit: Obwohl es ein UI-Test ist, wird er trotzdem automatisch wie ein üblicher Unittest ausgeführt. Daher ist er unten in der Pyramide zu finden.

Architekturfrage: BOs speziell für die UI-Schicht?
Angenommen, in einem Softwareprojekt werden speziell für die UI-Schicht Annotations verwendet. Das heißt, das BO mit Annotations wird nicht durchgehend verwendet, sondern nur für die UI-Schicht. Das wirft die allgemeinere Frage auf, ob es sinnvoll ist, besondere Business-Objekte (BO) allein für die Ansicht zu definieren. Neben einem BO „User“ wird noch ein „UserRegistrationForm“ definiert. Im Zusammenhang mit DataAnnotations wird dies an einem Beispiel „PersonForm“ hier dargestellt: [SPR21]. Damit hat man die Möglichkeit, Logik, die genau für diesen Teil der UI gedacht ist, isoliert zu testen. Man kann sogar noch weiter gehen, und BOs für unterschiedliche Anwendungsfälle erstellen. Etwa ein „UserRegistration“ oder ein „UserDeactivation“.

Diese Vorgehensweise kann in manchen Situationen die Rettung vor einzelnen, zu groß geratenen Klassen in der UI-Schicht sein. Es kann aber auch zu einer Ausuferung der Anzahl von Klassen kommen, wenn nun jedes BO für jeden Anwendungsfall eine andere, wenn auch passende, Implementierung in einer separaten Klasse bekommt. Ein Mittelweg wäre, für die gesamte UI-Schicht eine Klasse, etwa eine „UserUI“-Klasse, zu erstellen.

Testen wirkt
Durch Testen der Ansichtslogik wird die Architektur verbessert: Die Trennung von Ansichtslogik und Geschäftslogik wird besser durchgehalten. Die Unittests für die UI sollten wie die üblichen Unittests bereits beim Schreiben der eigentlichen Software begonnen werden. Auch das Ausführen soll wie die üblichen Unittests stattfinden: Innerhalb der Continuous Integration, aber auch während der Entwicklung.

Selbsttest: Testmöglichkeiten im Produkt

Ein weiteres unterschätztes Testverfahren ist der Selbsttest. Solche Tests laufen unter dem Namen „Diagnosetests“, „Health Monitor“ oder „Self-Checking-Tool“. Hier werden Daten In-Vivo, am „lebenden Objekt“ gesammelt.

Im Web-Bereich ist dies schon länger in Gebrauch. Das ist aus der Not entstanden. Der Admin-Bereich auf einer großen Webseite wurde zunächst programmiert, um die Webseite zu verwalten und Nutzerkonten zu konfigurieren. Bald darauf hat man festgestellt, dass es doch praktisch wäre zu wissen, was im Moment passiert. Zum Beispiel kann die Anzahl der Nutzer, die gerade eingeloggt sind, interessant sein. Wenn etwa die Webseite aus dringendem Grund offline gehen muss, wäre es geschickt, zu einem Zeitpunkt zu beginnen, wenn gerade möglichst wenige Nutzer angemeldet sind.

Diese Admin-Bereiche sind seit dem Beginn des Webs immer weiter gewachsen, auch weil sie der Fehlersuche dienen. Wie jeder Web-Entwickler weiß, kann schon die Anzeige der Uhrzeit des Servers der Schlüssel für so manchen Bugfix sein. Doch der zugrunde liegende Gedanke lässt sich verallgemeinern: Bei Desktop- und Mobile-Anwendungen sollen ebenfalls bequeme Diagnosemöglichkeiten geschaffen werden. „Bequemlichkeit“ ist ein gutes Stichwort, auch in der Qualitätssicherung. Je einfacher eine Diagnose zugänglich ist, desto öfter wird sie benutzt. Das gilt auch für die Entwickler. Fehler und Unzulänglichkeiten werden mit leicht ausführbaren, automatischen Tools früher im Entwicklungszyklus erkannt – und hoffentlich auch korrigiert. Daher hat Software über die Jahrzehnte immer bessere eingebaute Diagnosemöglichkeiten bekommen. Ein aktuelles Beispiel sieht man in Abbildung 5, der Debug-Bildschirm von Minecraft, der hier erklärt wird: [MCRA].

Abb. 5: Minecraft Debug-Anzeige

Selbsttest sind eine Form der Automatisierung. Eine Automatisierung ist schon dann sinnvoll, wenn ein Test für eine Situation vorgefertigt ist, sei es für den Support, für die Entwicklung oder die Schulung, und der Tester oder Benutzer ihn einfach & schnell ausführen können. Wenn ein Test nicht nur von Entwicklern ausgeführt werden soll, ist zu raten, ihn so zu gestalten, dass er sehr einfach zugänglich ist.

Eine andere Stoßrichtung ist der Test der Umgebung. Ein Diagnosetest bezüglich einer Installation kann etwa die Java-Version abfragen oder Verzeichnisse auf Berechtigungen prüfen [Cla04]. Weitere lohnende Prüfmerkmale [Cla04] sind:

  • Ist der Klassenpfad korrekt?
  • Kann die Verbindung zur Datenbank hergestellt werden?
  • Sind die Dateien (WAR, EAR, ...) an der richtigen Stelle?
  • Ist ein System oder eine URL erreichbar?

Der Nachteil eines integrierten Selbsttests ist, dass er bei Fehlern in der Installation eventuell gar nicht funktioniert, weil schon die Software insgesamt nicht startet. Dann muss man sich überlegen, ob nicht eine robustere Diagnoseanwendung außerhalb der eigentlichen Software geschaffen werden sollte.

Selbsttests prüfen typischerweise:

  • Verfügbarkeit von Backends,
  • Verfügbarkeit von lokalen Datenquellen (Leserechte, Schreibrechte),
  • Vollständigkeit der Installation,
  • KPIs wie Rate der Transaktionen, technische Vorgänge, z. B. Seitenabrufe pro Sekunde, Frames pro Sekunde,
  • Dauer von Transaktionen,
  • Systeminformationen,
  • Umgebung der Applikation wie Installationspfad, Speicherverbrauch, Uhrzeit des Servers.

Schreiben von Selbsttests: Red Flag „Datenquelle nicht verfügbar“

Nun gibt es viele Umgebungsparameter und Anbindungen, die abgefragt werden können. Auf welche soll man sich konzentrieren? Immer wenn ein Feature geschrieben wird, welches an vielen unsicheren Bedingungen hängt, ist anzuraten, eine leichte, niedrigschwellige Kontrollmöglichkeit einzuführen. Die Erfahrung zeigt: Wenn schon beim Schreiben eines Features die Umgebung öfters nicht voll verfügbar ist, also manche Anbindungen wackeln, dann ist dies immer ein Hinweis darauf, dass die entsprechenden Wackelkandidaten im Produktionsbetrieb ebenfalls nicht zuverlässig sein werden. Hier lohnt sich ein Diagnosetest schon während der Entwicklungszeit der 1. Version.

Wann werden Selbsttests ausgeführt

Diese Selbsttests sind unter verschiedenen Szenarien anwendbar. Der Normalfall ist, wenn ein Supporter auf Fehlersuche geht und den Test ausführt oder wenn ein User sich fragt, welche Features installiert sind oder welche Leistungskennziffern (KPIs) erreicht werden, etwa Frames-per-Second oder Transaktionen pro Sekunde. Weitere Anwendungsfälle, die meinem Eindruck nach zu selten in den offiziellen Entwicklungsprozess aufgenommen werden, deren Umsetzung ich jedoch auch bei Line-of-Business-Anwendungen empfehle:

  • Entwickler, der wissen will, ob seine Entwicklungsumgebung im Feld oder an seinem Arbeitsplatz korrekt aufgesetzt ist und welches Fehlerpotenzial sie bietet. (Sind meine Datenquellen immer da?)
  • Continuous Integration: Selbsttests können hier über UI-Tests ausgeführt werden. Oder, wenn eine geeignete API bereitsteht, auch im Rahmen der Integrationstest.
  • Tester: Kann die Umgebung der Tests schnell überprüfen. (Ist die Anwendung mit dem – richtigen – Testserver oder mit dem Produktivserver verbunden?)
  • Projektmanager, der die Performance im Feld explorativ testet.

Ressourcenintensive Tests sind typischerweise nur mit einem Superuser- oder Admin-Account zugänglich. Es wäre ungünstig, wenn ein normaler User mit einem Test eine große Last am Backend erzeugte.

Ökosystem & Abgrenzung: Monitoring & Management
Was mit Selbsttests nicht mehr abgedeckt wird, aber dennoch der Überwachung bedarf, wie etwa Server-Landschaften, sollte mit Monitoring-Tools überwacht werden. Diese Tools sind Eigenentwicklungen, basieren eventuell auf Frameworks wie JMX, Jolokia [JOLO] oder sind proprietäre Software. Die kommerziellen Produkte Nagios [NAGI] und SolarWinds [SOLA] sowie das quelloffene Prometheus [PROM] sind Beispiele für bekannte, große Monitoring-Produkte. Auch die Platzhirsche bieten große SW-Suiten an, „Oracle Management Cloud“ und Microsofts „Azure Monitor“, die entgegen der Namensgebung nicht nur für die Cloud, sondern auch für SW on-premises einsetzbar sind.

Eine Marktübersicht und einen Überblick über die verschiedenen Einsatzgebiete, auch im Hinblick auf Real User Monitoring (RUM) Testserver, hat Uwe Schulze unter dem Stichwort „Application Performance Monitoring” (APM) [Sch21] zusammengetragen: „Das zentrale Ziel beim Betrieb von Applikationen und Infrastrukturen ist die Automatisierung.Die APM-Tools stellen sie [die Analysedaten, Anm. von AR] in ihren Dashboards dar, die sich nach Kundenwunsch gestalten lassen ...“ [Sch21, S. 85]

Was ein Entwicklerteam aus dieser Fülle von Produkten lernen kann: Für die Selbsttests kann die Beschäftigung mit diesen gewaltigen Tools helfen, Anregungen dafür zu finden, was man selbst umsetzen will. Aber auch Hinweise, was wohl besser unterlassen wird, um nicht den Aufwand zu groß werden zu lassen. An den ausprogrammierten Features der großen Monitoringtools lässt sich vielleicht abschätzen, ob ein bestimmtes Feature für einen Selbsttest, der in der Entwicklung mit einigen Tagen oder Wochen Zeitaufwand erstellt werden soll, eine Nummer zu groß ist. So kann das Überwachen mit mehreren kollaborativen Agents oder ein nutzerdefiniertes Dashboard das Zeitbudget schnell sprengen.

Integrationstests mit Unittest-Frameworks

In manchen Projekten werden in Eigenentwicklung Frameworks erstellt, um Integrationstests durchzuführen. Die anderen Möglichkeiten für Integrationstests, nämlich Unittest-Frameworks oder integrierte Selbsttests zu verwenden, sollte jedoch nicht pauschal abgelehnt werden. Frameworks, gerade selbst geschriebene, generieren einen dauernden Verwaltungsaufwand, beim Programmieren, beim Testen, im Feld.

Schon Kent Beck hat darauf hingewiesen, dass man Unittests auch als Integrationstest, sogar mit Anbindung an Datenbank und Netzwerkdienste, verwenden kann. Isoliertes Testen mit Stubs hingegen erlauben schnellere Tests. Noch schlauer ist es, eine Wahlmöglichkeit zu schaffen, um „Ihre Tests sowohl für die lokale Entwicklung als auch für die Integrationstests verwenden zu können“ [Bec05, S. 41]. Beck empfahl also beides, denn das rein isolierte Testen ist nicht genug: „Was geht Ihnen verloren, wenn Sie Stubs benutzen? Wenn ein Stub das Verhalten des realen Objekts nicht sehr gewissenhaft reproduziert, gehen Sie das Risiko ein, dass Ihre Tests zwar erfolgreich sind, das echte System aber versagt.“ Beck empfahl, die Anbindung als Parameter zu übergeben. Heute ist dieses Pattern als Dependency Injection bekannt, es ist eine Umsetzung des Dependency Inversion Principle und steht für das „D“ in den SOLID-Prinzipien.

Automatische Integrationstests sind eine wichtige Säule
Besonders bei großen Datenmengen sind Integrationstests unverzichtbar. Schafft die gewählte Architektur den Umgang mit großen Datenmengen? So können mit Integrationstest auch die architektonischen Entscheidungen im wahrsten Sinne des Wortes einem Test unterzogen werden. Ist es wirklich eine gute Idee, 100.000 Datensätze vollständig in den Client zu laden, damit der Nutzer darin schnell navigieren kann? Was, wenn die Datensätze plötzlich nicht optimierte Testdaten enthalten, sondern die Spezifikationen ausreizen?

Ein Integrationstest kann technische Annahmen über die Anwendung schnell überprüfen. Stimmt das Mengengerüst der instanziierten Objekte oder sind die zugleich geladenen Listen zu übergewichtig? Für wie viele Datensätze ist die Tabelle ausgerichtet? Das analysiert ein Test mit Daten, die der Produktionsumgebung nachempfunden sind. Doch kommt die Anwendung damit unter verschiedenen Szenarien zurecht? Auch nur einen Bruchteil der möglichen Szenarien mit aufwendigen Systemtests auszuführen, führt zu einem zu großen Zeitbedarf in der Testabteilung. Das schreit nach Integrationstests!

Nur mit Unittest-Frameworks – im Enterprise-Umfeld?
Je größer und vernetzter die Software wird, desto mehr spielen auch externe Komponenten verschiedenster Art eine Rolle. „Extern“ sei hier im Sinne von „außerhalb des eigenen Projekts“ gemeint. Darunter fallen nicht nur Projekte wie Spring, sondern Komponenten und Datenquellen anderer Abteilungen und von Partnerunternehmen.

Im Integrationstest ist der Code oft binär davon abhängig, muss also referenziert werden. Wie kann man trotzdem mit Unittests die Integrationstests entwickeln? Die externen Komponenten, die nicht Gegenstand des aktuellen Integrationstests sind, müssen bei Integrationstests gemockt oder per Featureflag ausgeschalten werden, um den verbleibenden Teil integriert zu testen. Manche werden das nicht ermöglichen und müssen anderweitig „stillgelegt“ werden, etwa durch Test-Konfigurationen. Hier wird man nicht umhinkommen, für manche Integrationstests projektspezifischen Code zu schreiben. Natürlich soll dieser Code für viele Integrationstests wiederverwendbar sein – schon ist das hauseigene Integrationstestframework geboren. Projektspezifische und unternehmensspezifische Frameworks für Integrationstests sind oft unabdingbar, es bleibt, sie gut zu managen.

Kombination: Integrationstest als Selbstdiagnose
Damit sind Integrationstests gemeint, die im Endprodukt wie ein Diagnosetest enthalten sind. Es gibt also, um ein zugegebenermaßen provokantes Beispiel zu geben, einen Button, der 10 Kunden anlegt und wieder löscht. Der Nutzen eines solchen Tests kann sehr groß sein, der Schaden bei nicht sorgfältiger Implementierung und Absicherung durch klare Berechtigungsregeln ebenso. Denn es muss zum Beispiel ausgeschlossen sein, dass Tests mit kritischen Schreibaufträgen von unbedachten Benutzern auf die Produktionsdaten abgefeuert werden.

Fazit für Architektur & Projektmanagement

Per automatisiertem Testen durch Unittests der UI, Selbsttests und Integrationstests mit leichtem Zugang ist besser prüfbar, ob der Code im Einzelnen und die architektonischen Entscheidungen im Ganzen richtig sind. Diese Ansätze dienen auch dazu, stillschweigend gemachte Annahmen, etwa über Datenmengen und Übertragungsraten, offenzulegen. Testautomatisierung fordert neben Sorgfalt und wirtschaftlichem Denken auch Kreativität und Urteilsvermögen, um aus den vielen Ansätzen auszuwählen und die gewählten Ansätze passend für das Projekt umzusetzen.

Weitere Informationen

[ALC17]
J. Alcérreca, „Don’t let ViewModels (and Presenters) know about Android framework classes”, in: „ViewModels and LiveData: Patterns + AntiPatterns”, 2017, 
https://medium.com/androiddevelopers/viewmodels-and-livedata-patterns-antipatterns-21efaef74a54 

[Bal11]
H. Balzert, Lehrbuch der Softwaretechnik: Entwurf, Implementierung, Installation und Betrieb, Spektrum Akademischer Verlag, 2011

[BEA21]
https://beanvalidation.org/

[Bec05]
K. Beck, JUnit – kurz & gut, O’Reilly, 2005

[CHU18]
A. Chugh, „The Presenter holds references to the View. The ViewModel doesn’t" in: „Android MVVM Design Pattern”, 2018, 
https://www.journaldev.com/20292/android-mvvm-design-pattern

[Cla04]
M. Clark, Pragmatic Project Automation, The Pragmatic Programmers, 2004 , Kapitel 5.4: Troubleshooting with Diagnostic Tests, P. 103 (deutsche Ausgabe: Projektautomatisierung, 2006, S. 112), Prüfmerkmale siehe: US: P. 108 DE: S. 117 

[FOW07]
M. Fowler, Mocks Aren't Stubs, 2.1.2007, 
https://martinfowler.com/articles/mocksArentStubs.html

[GoF94]
E. Gamma, R. Helm, R. Johnson, J. Vlissides, Design Patterns – Elements of Reusable Object-Oriented Software, Addison-Wesley, 1993; Observer-Pattern S. 293

[HIB21]
http://hibernate.org/validator/

JOLO]
https://jolokia.org/

[MCRA]
https://minecraft-de.gamepedia.com/Debug-Bildschirm

[NAGI]
https://www.nagios.org/

[PROM]
https://prometheus.io

[Sch21]
U. Schulze, Marktübersicht Application Performance Monitoring, in: iX 3/2021, S. 80ff, Heise 

[SOLA]
https://www.solarwinds.com/

[SPR21]
Getting Started | Validating Form Input (spring.io), https://spring.io/guides/gs/validating-form-input/

[VIEW]
“The ViewModel class allows data to survive configuration changes such as screen rotations.”, “A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.”, 
https://developer.android.com/topic/libraries/architecture/viewmodel

. . .
Nächster Artikel
Praxis-Wegweiser

Author Image

Andreas Reif

Author
Zu Inhalten
Andreas Reif ist seit 20 Jahren Entwickler. Mit technischen und organisatorischen Mitteln die Qualität verbessern und die Effizienz steigern ist seine Leidenschaft.

Artikel teilen

Nächster Artikel
Praxis-Wegweiser