Testbarkeit von Software

Viele haben inzwischen gelernt, dass man Software in erster Linie für den Anwender schreibt und nicht für die Verwendung möglichst cooler Technologie. Das vertrete ich hier auch immer wieder, aber nun kommt doch ein kleiner Gedanke in die Gegenrichtung. Betrachten wir einmal nicht nur die eigentlichen Softwareentwickler, sondern auch Softwaretester, -architekten, diejenigen die die Software später installieren und betreiben, DBAs u.s.w. als das Team das am Ende die Software dem Endanwender zur Verfügung stellt. Ich habe gute Erfahrungen mit dieser Sichtweise gemacht. Es geht jetzt also darum, Software im Interesse der Effizienz dieses Teams, speziell der Softwaretester zu optimieren.

Es gibt es oft die Situation, dass sich reale Szenarien während des Software-Tests nicht beliebig gut nachstellen lassen, weil die umgebende Infrastruktur irgendwo aufhören muss und durch Simulationen ersetzt wird. Die Frage der Testdaten ist auch immer wichtig und oft wird aus Datenschutzgründen der Zugang zu den realen Daten erschwert und es wird mit mehr oder weniger guten synthetischen Daten getestet. Besonders interessant wird es bei Software, die ein Zeitverhalten aufweisen soll, z.B. einen Fahrplanwechsel zu einem bestimmten Zeitpunkt berücksichtigen. Man kann ein eigenes Testnetzwerk mit eigenem Timeserver aufbauen und dann Datum und Uhrzeit manipulieren. Weil so ein Testnetzwerk aufwendig ist, sieht man es nicht überall und wenn man es hat, wird es von vielen Anwendern gleichzeitig verwendet, so dass das Stellen der Uhr koordiniert ablaufen muss und nur wenige Tests möglich sind. Die Uhr bei einzelnen Rechnern zu verstellen ist verlockend, aber doch oft problematisch, weil die Applikation auf verschiedenen Servern läuft und es interessante Effekte gibt, wenn die Uhr nicht überall auf dieselbe Art verstellt wird. Der Timeserver ist ja oft so wichtig, damit alle Server dieselbe Uhrzeit haben und nicht einmal in erster Linie damit die Zeit richtig ist. Man hört, dass Google sogar in den größeren Rechenzentren jeweils eine Atomuhr aufstellt, um die Zeit synchron zu halten, was zwischen verschiedenen Rechenzentren wegen der Latenz der Netzwerkverbindungen gar nicht so einfach ist. Die Zeitstempel in übertragenen, gespeicherten und verarbeiteten Daten sind häufig sehr relevant, um zu entscheiden, in welcher Reihenfolge gewisse Ereignisse erfolgt sind. Für verteilte Applikationen und insbesondere für verteilte Datenbanken ist das sehr wichtig.

Dieses Beispiel zeigt es, dass es sich lohnen kann, bei der Softwareentwicklung die Testbarkeit zu berücksichtigen. Man sollte den Testern halbwegs effiziente und machbare Möglichkeiten bieten, ihre Arbeit zu machen. Software könnte also Wege anbieten, speziell mit der Zeit umzugehen, Zwischenergebnisse abzugreifen oder zu ändern, die im Produktiveinsatz eigentlich gar nicht relevant wären. So kann man die Zeit des Testens nutzbringender einsetzen, mehr Tests machen und mehr Fehler finden. Dass sich diese extrem aufwendigen Tests in einer idealen Testumgebung am Schluss auch noch lohnen, ist unbestritten, aber man bremst die ganze Entwicklung und damit den Teamerfolg aus, wenn man die Hürden für alle Tests zu hoch ansetzt.

Share Button

Automatisierte Tests: Unit-Tests

Bekanntlich sind Softwaretests sehr teuer. Erst einmal verursacht das Testen selbst schon viel Aufwand. Und dann ist es zumindest bei guten Testern noch schlimmer, weil dabei eventuell Fehler gefunden werden, deren Behebung dann auch noch einmal teuer ist.

Nun ist leider das Ignorieren von Fehlern auch teuer, wenn die Software für einen ernsthaften Zweck eingesetzt wird. Man sagt, daß es umso teurer wird, je später der Fehler gefunden wird. Das fängt teilweise schon vor der eigentlichen Entwicklung des entsprechenden Features an. Wenn man das falsche entwickelt oder nur auf das falsche Konzept setzt, dann wird der Aufwand, daraus am Ende eine brauchbare Lösung zu machen, auch groß. Das trifft auch bei agilen Entwicklungsprozessen zu.

Ein anderes, gar nicht so seltenes Ärgernis sind Fehler, die schon einmal behoben waren und irgendwann wieder auftauchen. Wie kann das passieren? Es ist leider so, daß man beim Arbeiten Fehler macht, außer vielleicht Donald E. Knuth bei der Entwicklung von TeX, das ja praktisch komplett fehlerfrei ist.

Hier geht es um automatisierte Tests, insbesondere während der Entwicklung, also vor allem Unit-Tests. Eine ganz neue Erfindung sind diese automatisierten Unit-Tests nicht. Schon in den 90er Jahren war es bei in C geschriebener Unix- und Linux-Software recht üblich, daß man nach
./configure
make
ein
make check
oder
make test
aufrufen konnte. Das gibt es so übrigens auch heute noch. TeX und Metafont von Donald E. Knuth haben den sogenannten „Trip“/“Trap“-Test, der recht rabiat vorgehen soll, also versucht, die Software „gegen die Wand zu fahren“.

Aber heute werden diese Unit-Tests durch leicht verfügbare Frameworks, wie z.B. JUnit für Java unterstützt oder sind sogar schon Teil des normalen Lieferumfangs der Sprache, wie bei Ruby. Sinnvoll ist es bei Projekten, die die entsprechende Infrastruktur haben oder haben können, diese Tests regelmäßig automatisch auf den neuesten Stand aus dem Repository anzuwenden und Fehler zu melden. Vielleicht sogar mit einer roten Fußgängerampel beim Ausgang des Gebäudes, in dem die Entwickler der Software arbeiten? 😉

Andererseits macht es auch Spaß, wenn die Unit-Tests nach einer heftigen Änderung durchlaufen und alles grün ist, sogar die Fußgängerampel bei der Tür.

Nun ist die Frage, wie man dieses Mittel einsetzt, wie weit man automatisiserte Tests treibt und wann man sie entwickelt.

Erfahrungsgemäß werden Unit-Tests gerne bei der Zeitabschätzung eingeplant. Später wird dann die Zeit knapp und sie werden dann doch weggelassen. Es gibt eigentlich häufiger zu wenige als zu viele solcher Unit-Tests. Warum kann es überhaupt zu viele geben? Natürlich wird es irgendwann lästig, wenn das Ausführen der Unit-Tests mehrere Stunden dauert und wenn die Entwicklungsaufwände für die Tests unverhältnismäßig groß werden. Wobei ich in manchen Fällen 60% des Aufwands für die Tests und 40% für die eigentliche Funktionalität noch für angemessen halte. Das Problem ist aber, daß man einige Dinge nur mit sehr großem Aufwand automatisiert testen kann. Damit geht die Flexibilität verloren. Änderungen, die man mal eben macht, verhindern schnell mal, daß die Tests erfolgreich durchlaufen. Dann werden sie mal kurz „vorläufig“ deaktiviert, auskommentiert oder sogar ganz gelöscht, weil sie nicht mehr anwendbar sind, ohne daß die entsprechende neue Funktionalität Tests bekommt. Oder man erinnert sich, wie mühsam es war, die Tests zu schreiben und verzichtet dann deshalb auf eine an sich sinnvolle Änderung.

Daher würde ich empfehlen, automatisierte Tests für Funktionalitäten, die sich noch oft ändern, eher in einer späten Phase des Projekts zu schreiben. Dagegen lohnt es sich für Basisfunktionalitäten, von denen im Gesamtsystem viel abhängt und die sich wahrscheinlich kaum ändern, höchstens erweitert werden, sehr umfangreiche Unit-Tests.

Schön ist es, wenn man beim Beheben von Fehlern „Test Driven Development“ praktiziert, also einen oder mehrere Unit-Tests schreibt, die eigentlich erfolgreich („grün“) durchlaufen sollten, aber die aufgrund des Fehlers scheitern. Das schaut man sich an, es muß wirklich „rot“ werden, und zwar wegen des Fehlers, den man gerade behebt, sonst ist der Test falsch. Dann behebt man den Fehler und am Schluß läuft der neue Test erfolgreich durch. Weil man ihn dann in die Menge der automatisiert aufgerufenen Unit-Tests aufnimmt, ist die Wahrscheinlichkeit, daß derselbe Fehler bei einer späteren Änderung wieder hereinkommt, sehr verringert worden.

Share Button