Herausforderung Brownfield, Teil 3: Das Sicherheitsnetz erweitern

Die Angst vor Regressionsfehlern ist sicherlich die größte Angst bei Brownfield-Projekten. Erst wenn Fehler schnell zu erkennen sind, können Entwickler ohne Angst Veränderungen am Quellcode vornehmen. Um die Sicherheit zu erlangen, benötigen sie automatisierte Tests.

In Pocket speichern vorlesen Druckansicht
Lesezeit: 19 Min.
Von
  • Stefan Lieser
  • Ralf Westphal
Inhaltsverzeichnis

Die Angst vor Regressionsfehlern ist sicherlich die größte Angst bei Brownfield-Projekten. Da Entwickler nicht sicher sein können, dass Änderungen keine Probleme bereiten, lassen sie lieber die Finger weg vom Quellcode. Doch auf die Weise wird aus dem "big ball of mud" keine grüne Wiese. Erst wenn die Programmierer sicher sein können, dass Fehler schnell zu erkennen sind, können sie ohne Angst Veränderungen am Quellcode vornehmen. Um die Sicherheit zu erlangen, benötigen sie automatisierte Tests.

Durch die Einführung von Versionskontrolle und Continuous Integration hat ein Brownfield-Projekt einen Stand erreicht, bei dem sich die Entwickler trauen können, Änderungen vorzunehmen. Schließlich liegen der gesamte Quellcode und alle anderen Artefakte in der Versionsverwaltung, wodurch nichts verloren gehen kann. Ferner lässt sich zu jedem Zeitpunkt ein früherer Versionsstand rekonstruieren. Das ist schon viel Wert, nimmt aber noch nicht die Angst, dass nach einer Änderung tatsächlich noch alles so funktioniert wie vorher. Zwar erkennt man durch den Continuous-Integration-Prozess sofort den ungünstigsten Fall, dass die Anwendung gar nicht mehr zu übersetzen ist. Doch ob das übersetzte Resultat noch funktioniert, weiß niemand.

Mehr Infos

Herausforderung Brownfield

Im Rahmen einer Artikelserie beleuchten die Initiatoren der "Clean Code Developer"-Initiative, Stefan Lieser und Ralf Westphal, die Herausforderungen an Clean Code Developer in Brownfield-Projekten.

Wie eingangs angerissen, ist die Einführung von automatisierten Tests dringend erforderlich, um die Sicherheit zu erlangen, dass Entwickler bedenkenlos Änderungen am Quellcode vornehmen können. Doch wie soll das bei einem Brownfield-Projekt konkret geschehen? Schließlich wurde es ja nicht nach den Regeln der Kunst implementiert. Folglich steht zunächst der Verdacht im Raum, dass automatisierte Tests in einem solchen Projekt nicht möglich sind.

So viel sei gleich verraten: Ein paar automatisierte Tests gehen immer. Bevor zu klären ist, welche Mittel für automatisierte Tests in Brownfield-Projekten existieren, sei differenziert, welche Arten von Tests es gibt. Ein
wichtiges Kriterium für automatisierte Tests ist die Unterscheidung in Integrations- und Unittests. Die Abgrenzung klingt zunächst einfach: Bei einem Unittests testet man isoliert eine einzelne Unit. Dabei meint "isoliert", dass man keine andere Unit des Produktionscodes in den Tests verwendet. Schwieriger gestaltet es sich, sobald man der Frage nachgeht, was sich denn als Unit bezeichnen lässt. Unit sei deswegen zunächst mal mit Funktionseinheit übersetzt. Sie lässt sich in objektorientierten Programmiersprachen durch Methoden, Klassen, Komponenten, Prozesse et cetera bilden. Folglich ist der Begriff Unit vor allem nicht gleichzusetzen mit Klasse.

Betrachtet man Methoden als Funktionseinheiten auf der untersten Ebene, sind auf ihr Unittests so definiert, dass ein Test eine einzelne Methode in Isolation testet. Die Methode darf keine anderen Methoden aus dem Produktionscode verwenden – und auch keine Funktionseinheiten der höheren Ebenen. Auf der nächsten Ebene stehen die Klassen. Auf ihr lassen sich Tests einer einzelnen Klasse isoliert durchführen. Eine Klasse darf keine andere des Produktionscodes verwenden. Gleiches gilt für die weiteren Ebenen. Das Prinzip gilt auch beim isolierten Testen von Komponenten und Prozessen.

Verwendet eine Funktionseinheit im Test andere Einheiten des Produktionscodes, spricht man von Integrationstests. Sie sind auf den unterschiedlichen Abstraktionsebenen umzusetzen. Testet man eine Methode so, dass sie andere Methoden des Produktionscodes verwendet, spricht man auf der Ebene der Methoden von einem Integrationstest. Auf der Klassenebene betrachtet mag es sich jedoch um einen Unittest handeln, nämlich wenn alle im Test verwendeten Methoden aus derselben Klasse stammen.

Es sei erwähnt, dass sich die Begriffe Unit- und Integrationstest üblicherweise nicht so exakt abgrenzen lassen, wie das hier geschieht. Meist ist von einem Unittest die Rede, wenn man eine Klasse isoliert testet. Von Integrationstest spricht man oft, wenn die komplette Anwendung von der Benutzerschnittstelle bis "nach unten" hindurch zur Datenbank oder anderen Ressourcen getestet wird. Es ist erforderlich, das Abstraktionsniveau einzubeziehen, weil es vor allem beim Begriff Integrationstest sonst zu Verwechslungen kommen mag.

Das Isolieren der zu testenden Funktionseinheiten ist ein Ziel, das Entwickler bei Brownfield-Projekten in der Regel nicht von Anfang an erreichen. Warum das Testen in Isolation so wichtig ist, mag die Analogie zum Automobilbau zeigen. Dort ist es selbstverständlich, einzelne Teile isoliert zu testen. Es gibt beispielsweise Motorenprüfstände, auf denen sich der Motor eines Autos gesondert testen lässt. Der Motor muss sich also zum Testen nicht in einem Auto befinden. Das hat unbestreitbar Vorteile: Man stelle sich ein Testteam vor, das bei Tempo 200 auf der Autobahn versucht, den Motor unter den Betriebsbedingungen zu testen. Um den Motor isoliert testen zu können, muss der Prüfstand die erforderlichen Anschlüsse bieten, damit der Motor so zu betreiben ist, als wäre er in ein Auto eingebaut. Neben Spritzufuhr und Steuerungssignalen sind andere Rahmenbedingungen zu schaffen, die ein Motor in eingebautem Zustand erfährt. Insbesondere ist ihm ein Widerstand entgegenzugestellen, damit er nicht leer läuft.

Übertragen auf die Softwareenwicklung benötigen Entwickler Prüfstände für Methoden, Klassen, Komponenten et cetera. Doch zurück zum Sinn des isolierten Testens. Bei der Software ist es wichtig, Funktionseinheiten isoliert testen zu können. Das hat mehrere Gründe:

  • Aufbau der Testumgebung
  • Erzeugen von Testdaten
  • Ablaufgeschwindigkeit der Tests
  • Erkennen der Fehlerursache