3.7 Prinzip 7: Mach es testbar
PKW-Motoren brauchen Öl, damit sie funktionieren. Sie benötigen aber keinen Ölmessstab. Und doch werden alle PKW-Motoren mit einem Ölmessstab ausgeliefert, und niemand zweifelt am Sinn dieser Konstruktion. Autos sind auch ohne einen Ölmessstab fahrtüchtig, und wenn kein Öl mehr da ist, kann man das auch auf anderem Wege feststellen.
Doch der Ölmessstab hat einen großen Vorteil: Er ermöglicht uns, eine Komponente des Motors, den Ölpegel, separat von anderen Komponenten zu überprüfen. Und so können wir, wenn das Auto streikt, schnell erkennen, dass wir zu wenig Öl haben; noch bevor wir die Zündkerzen unnötigerweise ausgebaut und überprüft haben.
Software ist eine komplexe Angelegenheit, bei deren Erstellung Fehler passieren. Es ist sehr wichtig, diese Fehler schnell zu entdecken, sie schnell zu lokalisieren. Deswegen ist es sehr wertvoll, wenn sich die einzelnen Komponenten der Software separat testen lassen.
Genau wie bei der Konstruktion der Motoren werden auch in der Softwareentwicklung manche Designentscheidungen getroffen, nicht um die Funktionalität der Software zu verbessern oder um eine Komponente mehrfach verwendbar zu machen, sondern um sie testbar zu machen.
Die moderneren Autos mit einem Bordcomputer überprüfen den Ölstand automatisch. Das ist sehr bequem. Man braucht den Ölmessstab nur dann zu benutzen, wenn der Bordcomputer ein Problem meldet.
Auch in der Softwareentwicklung kann man vieles automatisch testen lassen, und erst wenn die Tests ein Problem melden, muss man sich selbst auf Fehlerjagd begeben.
Die populärsten automatisierten Tests sind die Unit-Tests.
Unit-Tests |
Ein Unit-Test ist ein Stück eines Testprogramms, das die Umsetzung einer Anforderung an eine Softwarekomponente überprüft. Die Unit-Tests können automatisiert beim Bauen von Software laufen und so helfen, Fehler schnell zu erkennen. |
Schwierige Tests
Idealerweise werden für jede angeforderte Funktionalität einer Komponente entsprechende Unit-Tests programmiert. Idealerweise. Wir leben aber nicht in einer idealen Welt, und manche Komponenten automatisiert zu testen ist schwierig. Wie soll man z. B. automatisiert testen, dass eine Eingabemaske korrekt dargestellt wurde? Soll man einen Schnappschuss des Bildschirmes machen und ihn mit einer vorbereiteten Bilddatei vergleichen? Was passiert, wenn der Entwickler seine Bildschirmeinstellungen ändert? Wie testet man, dass die Datenbank korrekt manipuliert wurde? Muss man die Daten jederzeit zurücksetzen? Das dauert aber sehr lange.
Das Programmieren guter Unit-Tests ist nicht leicht. Es kann sogar viel aufwändiger als die Implementierung der zu testenden Funktionalität selbst sein. Es kann aber auch nicht Sinn der Sache sein, die Komplexität der entwickelten Anwendung niedrig zu halten, dafür aber die Komplexität des Tests explodieren zu lassen.
Stattdessen versucht man, die Gesamtkomplexität der Entwicklung zu reduzieren. Und das führt häufig dazu, dass die Module auch wegen ihrer Testbarkeit getrennt werden.
So kann man die Komponente, die eine Eingabemaske bereitstellt, in zwei Teile trennen. In Teil 1 werden die Eigenschaften der dargestellten Steuerelemente vorbereitet, ohne auf ihre tatsächliche Darstellung achten zu müssen. So kann man leicht automatisiert überprüfen, ob z. B. das Eingabefeld name gesperrt und die Taste löschen aktiviert ist. In einer anderen Komponente 2 werden dann die jeweiligen betriebssystemspezifischen Steuerelemente programmiert – diese können manuell getestet werden, ihre Implementierung wird sich nicht so häufig ändern.
Mock-Objekte
Oder man trennt die Bearbeitungslogik von der Anbindung an eine konkrete Datenbank. Dann kann man sie statt mit der langsamen Datenbank lieber mit einer sehr schnellen Ersatzdatenbank testen. Die Ersatzdatenbank braucht keine wirkliche Datenbank zu sein, es reicht, wenn sie die für den Test geforderte Funktionalität liefert. Solche Ersatzkomponenten, die nur zum Testen anderer Komponenten existieren, werden Mock-Objekte genannt.
Auswirkungen auf das Design
Erkennen Sie, wohin das führt? Sie trennen die Komponenten, nur um sie leichter testbar zu machen, und plötzlich stellen Sie fest, dass sie nicht mehr von der konkreten Implementierung anderer Komponenten abhängig sind, sondern von Abstraktionen mit einer kleinen Schnittstelle.
Das Streben nach Verringerung der Gesamtkomplexität der Entwicklung, der entwickelten Komponenten und der Tests führt dazu, dass die Verwendbarkeit der Komponenten erleichtert wird. Die Schwierigkeiten, bestimmte Tests zu entwickeln, zeigen Ihnen, wo Sie mehr Abstraktionen brauchen, wo Sie die Umkehr der Abhängigkeiten einsetzen können, wo Sie eine Schnittstelle explizit formulieren müssen und wo Sie Erweiterungspunkte einbauen sollten.
Durch die konsequente Erstellung der Unit-Tests wird also nicht nur die Korrektheit der Software sichergestellt, sondern auch das Design der Software verbessert.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.