Herausforderung Brownfield, Teil 3: Das Sicherheitsnetz erweitern

Seite 2: Gründe für isoliertes Testen

Inhaltsverzeichnis

Um eine Funktionseinheit automatisiert testen zu können, ist eine Testumgebung aufzubauen. Das fällt umso leichter, desto weniger Abhängigkeiten die zu testende Funktionseinheit hat. Denn alle Abhängigkeiten sind im Test zur Verfügung zu stellen. Das Aufbauen der Testumgebung gestaltet sich also umso schwieriger, desto mehr Korrelationen die zu testende Funktionseinheit hat. Ferner hängt der Aufwand davon ab, wie schwer es fällt, die einzelnen Abhängigkeiten bereitzustellen.

Ist die Funktionseinheit darauf ausgelegt, die Abhängigkeiten von außen zu erhalten, ist das automatisierte Testen vergleichsweise einfach. Schwieriger gestaltet es sich, wenn die Funktionseinheit die Abhängigkeiten selbst beschafft. Dann ist es oft nicht umsetzbar, für den Test spezielle Abhängigkeiten bereitzustellen, nämlich die über Messinstrumente verfügen.

Übertragen sei das noch mal auf den Test eines Motors: der benötigt eine Kraftstoffzufuhr. Wäre sie mit dem Motor verbunden, sodass keine Möglichkeit bestünde, sie auf dem Prüfstand durch eine andere zu ersetzen, würde der Motor samt Spritschlauch und Tank auf den Prüfstand gestellt. Wollte man nun den Spritverbrauch während eines Testlaufs ermitteln, müsste man dazu in den Tank schauen. Klingt kompliziert? Ist es auch. Selbstverständlich verfügt der Motor über einen (möglicherweise sogar genormten) Anschluss für die Spritzufuhr, und selbstverständlich wird dort auf dem Prüfstand eine Spritzufuhr angeschlossen, an der sich der Durchfluss messen lässt.

Nichts anderes benötigt der Entwickler für Softwarefunktionseinheiten: sozusagen Funktionseinheiten mit "Durchflussmesser". An die zu testende Funktionseinheit lassen sich im Idealfall also andere so "anschließen", dass man während des Tests beobachten kann, wie sich die zu testende Funktionseinheit gerade verhält.

Nachdem die zu testende Funktionseinheit bereitsteht, müssen Testdaten her. Je mehr "echte" Funktionseinheiten der Test verwendet, desto aufwendiger ist das Bereitstellen der Testdaten. Nutzt man nämlich für eine Abhängigkeit lediglich eine Attrappe anstelle der echten Funktionseinheit, sind die Testdaten einfacher zur Verfügung zu stellen.

Ein Beispiel mag das verdeutlichen: Man denke sich eine zu testende Funktionseinheit, die eine andere Funktionseinheit für die Überprüfung von Kreditkartendaten benötigt. Lässt sich die Kreditkartenprüfung im Test durch eine Attrappe ersetzen, kann man sie anweisen, die Gültigkeitsprüfung positiv oder negativ zu beantworten. Dafür sind keine Testdaten nötig. Will man im Test jedoch auf die "echte" Kreditkartenprüfung zugreifen, sind Testdaten in Form von Kreditkarteninformationen bereitzustellen. Die Daten müssen dazu führen, dass die Prüfung positiv beziehungsweise negativ ausfällt. Mithin sind also detaillierte Kenntnisse über die Kreditkartenprüfung erforderlich, um die Testdaten zur Verfügung stellen zu können.

Das Erzeugen von Testdaten sollte so einfach wie möglich sein. Nicht nur, weil sich sonst die automatisierten Tests nicht effizient erstellen lassen, sondern auch, weil sich in den Testdaten subtile Verletzungen des Prinzips "Don't Repeat Yourself" verbergen können. Das oben geschilderte Beispiel der Kreditkartenprüfung würde dazu führen, dass die Details zum Prüfungsverfahren über mehrere Stellen verteilt in Tests auftreten.

Die Geschwindigkeit, mit der automatisierte Tests ablaufen, hat einen hohen Einfluss darauf, wie oft sich die Tests ausführen lassen. Nur Tests, die schnell ablaufen, führt oft der Entwickler aus. Sobald ein Test länger läuft als ein paar Millisekunden, summieren sich die Laufzeiten so auf, dass ein Entwickler nur noch einzelne Tests ausführt (wenn überhaupt). Damit lassen sich die automatisierten Tests allerdings ad absurdum führen. Schließlich sollen sie eine schnelle Rückmeldung an den Entwickler geben, damit er merkt, dass etwas schief läuft. Je schneller die Rückmeldung eintrifft, desto einfacher ist es, den Fehler mit den Änderungen am Code in Beziehung zu setzen. Erfolgt die Rückmeldung erst nach mehreren Minuten oder sogar Stunden, muss der Entwickler erst nachdenken, welche Änderungen er überhaupt vorgenommen hat. Erfolgt die Rückmeldung unmittelbar nach einer Änderung, ist kein langes Nachdenken notwendig.

Damit Tests schnell ablaufen, müssen sie möglichst von der Infrastruktur unabhängig sein. Sobald ein Test Infrastruktur wie das Dateisystem oder eine Datenbank benötigt, ist die Ablaufgeschwindigkeit deutlich reduziert. Natürlich lässt sich das nicht in allen Fällen vermeiden. Aber Tests, die von Infrastruktur abhängen, sollten die Ausnahme sein.

Ein weiterer Grund für die Forderung, Funktionseinheiten isoliert testen zu können, liegt im Erkennen der Fehlerursache. Je weniger isoliert der Entwickler eine Funktionseinheit testet, desto größer ist der Einfluss anderer Einheiten auf das Testergebnis. Denn nicht selten lassen sich beim Testen einer Funktionseinheit Fehler in anderen Funktionseinheiten feststellen. Je mehr der Einheiten also den Test durchlaufen, desto mehr Fehlerquellen sind in Betracht zu ziehen.

Die Forderung, Funktionseinheiten isoliert testen zu können, soll nicht zum Ausdruck bringen, dass Tests über mehrere integrierte Funktionseinheiten keinen Wert hätten. Solche Tests sind selbstverständlich ebenfalls notwendig. Allerdings liegt dabei der Schwerpunkt auf Problemen, die bei der Integration auftreten können, nicht auf Problemen innerhalb der Funktionseinheiten. Erst durch die Trennung in Unit- und Integrationstests erreicht der Entwickler überhaupt eine vernünftige Testabdeckung.

Beispielsweise ziehe man dazu zwei Funktionseinheiten A und B heran, die eine dritte namens C verwendet. Wenn in A und B nur jeweils drei unterschiedliche Eingaben möglich sind, sind für A und B je drei Unittests zu schreiben. Sie prüfen, ob sich A beziehungsweise B bei den drei Eingaben korrekt verhalten. Ergänzt sei das Szenario um Tests für C, die zeigen, dass C die beiden Funktionseinheiten A und B korrekt verwendet. Ein vollständiger Test aller Kombinationsmöglichkeiten erscheint zwar noch realisierbar, ist in der Praxis aber unmöglich zu bewerkstelligen. Folglich muss der Entwickler sich bei den Testfällen so einschränken, dass einige typische Fälle abgedeckt sind. Das ist umso einfacher, je isolierter sich einzelne Funktionseinheiten betrachten lassen. Damit steht mfest: Unittests sind anzustreben, auch bei Brownfield-Projekten.