Herausforderung Brownfield, Teil 3: Das Sicherheitsnetz erweitern
Seite 3: Was tun bei Brownfield?
Was tun bei Brownfield?
Ist das Isolieren von Funktionseinheiten auf einer bestimmten Abstraktionsebene nicht möglich, ist die betreffende Funktionseinheit übergangsweise in einem Integrationstest zu testen. Das trifft dann zu, wenn zum Beispiel die Abhängigkeiten einer zu testenden Klasse zu anderen Klassen dergestalt sind, dass sich die zu testende Klasse nicht aus den Abhängigkeiten herauslösen lässt. Man kann für den Test alle benötigten Klassen instanziieren. Verglichen mit dem Testen eines Motors könnte das etwa bedeuten, den Motorraum samt Halterungen zu "instanziieren", um darin den Motor zu betreiben. Das ist immer noch besser, als zum Testen des Motors mit dem Auto über die Autobahn fahren zu müssen.
In der Regel lassen sich in Brownfield-Projekten, zu denen bislang keine automatisierten Tests existieren, keine Unittests entwickeln, ohne vorher die Struktur zu verändern. Doch bevor der Programmierer sie verändert, benötigt er einen Mechanismus, der sicherstellt, dass die Anwendung hinterher noch so funktioniert wie vor der Änderung. Der Ausweg liegt in Integrationstests. Im ersten Schritt betrifft das meist die gesamte Anwendung. Sie ist mit allen Abhängigkeiten zu starten, um automatisiert Tests durchführen zu können. Dazu gehört, dass sich die Anwendung immer in einem definierten Zustand starten lässt. Das betrifft zum Beispiel durch die Anwendung benötigte Ressourcen wie eine Datenbank. Anschließend sollte sich die Anwendung möglichst automatisiert bedienen lassen, um ein gewünschtes Ergebnis zu erzielen, welches der Test überprüft.
Je nach eingesetzter Technik sind die einzelnen Schritte unterschiedlich anspruchsvoll. Immerhin gibt es fĂĽr alle UnterstĂĽtzung durch Tools, und der Entwickler muss die automatisierte Bedienung der Anwendung nicht erst entwickeln. Das gilt sowohl fĂĽr Desktop- als auch fĂĽr Webanwendungen. Eine Ăśbersicht mit einigen Tools gibt es unter anderem hier.
Definierten Ausgangszustand herstellen
Das Bereitstellen der für die Anwendung benötigten Ressourcen mag für eine Datenbank noch relativ einfach sein. Greift die Applikation jedoch auf externe Ressourcen wie Webservices zu, gestaltet es sich schwieriger, sie für den Test bereitzustellen. Die Anwendung sollte dann zumindest soweit konfigurierbar sein, dass man ihr im Test spezielle Testressourcen zur Verfügung stellen kann. Ist das nicht möglich, stellt das einen ersten erforderlichen Schritt der Veränderung dar. Die Änderung der Anwendung ist zwar nicht durch einen automatisierten Test abgedeckt, aber irgendwo muss der Entwickler eben beginnen. Auch sollte er die Vorgehensweise mit dem Ausgangszustand der Anwendung vergleichen. Jetzt haben Entwickler Versionskontrolle und Continuous Integration als Sicherheitsnetz. Im Zweifelsfall lassen sich die Änderungen ohne Aufwand rückgängig machen, denn es ist jederzeit nachzuvollziehen, welche Änderungen überhaupt vorgenommen wurden.
Ein erstes Ziel stellt somit die Isolation der benötigten Ressourcen dar. Erst wenn es zwischen der Anwendung und den von ihr benötigten Ressourcen eine klare Trennlinie gibt, ist man in der Lage, die Ressourcen im Test durch Attrappen zu ersetzen. Das Ziel ist in der Praxis nicht selten nur mit viel Aufwand zu erreichen. Oft sind nämlich die Zugriffe auf Ressourcen "wild" über die gesamte Anwendung verteilt. Das geht los bei Datenbankzugriffen direkt in einer Form und endet bei Zugriffen auf den HttpContext in Webanwendungen. Erst wenn die Ressourcen klar von der Anwendungslogik getrennt sind, spielen sie in automatisierten Tests keine Rolle mehr. Solange die Trennung nicht erreicht ist, werden sie sich immer wieder in die Tests "drängeln" und verhindern, dass man eine Funktionseinheit isoliert testen kann.
Automatisierte Bedienung
Stehen die von der Anwendung benötigten Ressourcen in einem definierten Zustand bereit, lässt sich die Anwendung starten. Anschließend ist sie automatisiert zu bedienen. Das bedeutet meist, dass man die Applikation durch die Benutzeroberfläche hindurch steuert. Nur wenn der Entwickler die Benutzeroberfläche vom Kern des Programms getrennt hat, kann er die Steuerung unmittelbar unter der Oberfläche ansetzen. In aller Regel ist das jedoch nicht möglich, weil in der Benutzeroberfläche wichtige Anwendungsteile stecken, sie also ohne die Oberfläche gar nicht funktionsfähig ist.
Fortgeschrittene Werkzeuge zur Automatisierung der Anwendung können die Bedienung der Anwendung im Hintergrund aufzeichnen. Man startet die Applikation unter Aufsicht des Tools, bedient sie ganz normal und kann die einzelnen Bedienungsschritte später beliebig oft wieder abspielen. Bei der Fernsteuerung der Anwendung ist es notwendig, die Steuerelemente zu identifizieren, auf die sich eine Eingabe beziehen soll. Beispielsweise ist ein bestimmtes Eingabefeld erst auszuwählen, bevor sich dort ein Wert eingeben lässt. Ein erster Ansatz dazu wäre, die Mausbewegungen zu verfolgen und Steuerelemente über ihre Position zu identifizieren. Der Ansatz hat den großen Nachteil, dass schon kleine Veränderungen am Layout des Formulars dazu führen, dass die automatisierte Bedienung der Anwendung nicht mehr funktioniert. Ein anderer Ansatz ist die Identifikation der Steuerelemente über einen eindeutigen Identifizierer. Sowohl in Web- als auch in Desktopanwendungen weist man den Steuerelementen üblicherweise Namen zu. Verweisen die Aufzeichnungen auf die Namen, sind die Tests stabiler gegenüber Layoutänderungen.
PrĂĽfen der Ergebnisse
Am Ende soll die automatisierte Bedienung der Anwendung zu einem überprüfbaren Ergebnis führen. Lässt sich das Ergebnis innerhalb der Benutzeroberfläche überprüfen, benötigt man eine Option, aus dem Tests auf Steuerelemente der Oberfläche zugreifen zu können. Dazu ist es von Vorteil, wenn die Aufzeichnung der Anwendungssteuerung als Ergebnis im Quellcode vorliegt. Es ist nämlich relativ einfach, die Aufzeichnung um Prüfungen zu ergänzen, die feststellen, ob in den jeweiligen Steuerelementen der erwartete Inhalt steht.
Ein anderes Mittel besteht darin, den Zustand der verwendeten Ressourcen zu überprüfen. Kommt beispielsweise eine Datenbank zum Einsatz, lässt sich im Test prüfen, ob dort erwartete Datensätze vorhanden sind. Die Prüfungen erfolgen programmatisch in den Tests, stellen also in der Regel keine große technische Herausforderung dar. Problematisch an solchen Tests ist eher, dass sie das konkrete Datenbankschema verwenden. Daraus folgt, dass bei Änderungen am Schema meist auch Tests zu ändern sind. Im Idealfall stellt die Persistenz aber ein Implementierungsdetail dar, das keinen Einfluss auf die Anwendungslogik hat. Hier zeigt sich wieder, wie wichtig es ist, den Ressourcenzugriff von der Anwendungslogik zu trennen.