Freiwillige Selbstkontrolle
Kann Software ihre Qualität eigenständig sichern?
von Barbara PaechSoftware gilt als „Werkstoff des Informationszeitalters“ und Software Engineering als die „Produktionstechnik des 21. Jahrhunderts“. Viele Bereiche von Gesellschaft und Wirtschaft sind heute davon abhängig, dass die Software gelungen gestaltet ist und sich zuverlässig nutzen lässt. Die Softwareentwicklung sollte deshalb ähnlich hohen Qualitätsansprüchen genügen wie industrielle Fertigungsprozesse.
Leider stellt sich dies in der Praxis als schwierig heraus. Drastische Zeit- und Kostenüberschreitungen und gravierende Qualitätsmängel beim ersten Einsatz kennzeichnen viele Software-Entwicklungsprojekte. Ein klassisches Beispiel ist der Absturz der Ariane 5-Rakete im Jahr 1996, bei dem die Nutzlast zerstört und Kosten von über 500 Millionen Euro verursacht wurden. Dass es so weit kommen konnte, ist auf eine Vielzahl typischer Fehler während der Softwareentwicklung zurückzuführen.
Hauptauslöser war, dass Teile der für Ariane 4 vorgesehenen Software unreflektiert in die Ariane 5 übernommen wurden und Maßnahmen zu deren Qualitätssicherung nur sehr eingeschränkt erfolgten. Eine gründliche Qualitätssicherung hätte diese Fehler erkannt, beispielsweise durch ausführliches Testen, durch das systematische Überprüfen aller Entwurfsentscheidungen oder durch das Implementieren weit reichender Fehlertoleranzmechanismen. Im Alltag der industriellen Softwareprojekte sind die Folgen meist weniger drastisch. Das Problem besteht aber darin, das ein Viertel der Projekte ergebnislos abgebrochen wird und sich bei der Hälfte der Zeit- und Kostenbedarf erhöht, teilweise gar verdoppelt.
Was genau bedeutet Qualität von Software? Typischerweise unterscheidet man Aspekte der Funktionalität, der Benutzbarkeit, der Effizienz, der Zuverlässigkeit, der Änderbarkeit und der Übertragbarkeit. Typische Fragen sind also: Unterstützt die Software die wichtigen Funktionen, können Benutzer sie leicht bedienen, werden nicht zu viel Ressourcen (Zeit und Hardware) verbraucht, ist die Software robust gegen Fehlbedienung und Umgebungsfehler, kann man die zugrunde liegenden Algorithmen und Datenstrukturen leicht verstehen und ergänzen, lässt sich die Software einfach auf andere Ausführungsumgebungen übertragen? Dass diese Fragen selbst bei Auslieferung der Software nicht immer mit „Ja“ beantwortet worden sind, merkt der Nutzer erst, wenn er sich über Abstürze, unübersichtliche Menüs, etwa in der Textverarbeitung, oder über lange Wartezeiten, beispielsweise während der Suche im Internet, ärgert.
Die Frage ist also: Wie können Qualitätsprobleme frühzeitig erkannt werden? Im Unterschied zu indus- triellen Produkten, etwa dem Herstellen von Waschmaschinen oder Mobiltelefonen, gibt es bei Software keinen Fertigungsprozess. Die Software wird schrittweise von der ersten Idee über konzeptuelle Entwürfe und Prototypen ausgearbeitet. Dabei werden einzelne Bestandteile, so genannte Komponenten, nach und nach fertiggestellt und anschließend zusammengefügt. Eine Komponente gleicht dabei einem Puzzleteil, das einen klar umrissenen Teil der Gesamtfunktionalität erbringt.
Fertige Software ist verhältnismäßig leicht zu vervielfältigen. Idealerweise wird sie während des Erstellens gründlich getestet. Es werden dabei mögliche Abläufe der Software geprüft, um zu sehen, ob sie auch tatsächlich das erwartete Verhalten zeigen. Diese Tests können jedoch nur bei einem sehr kleinen Teil aller möglichen Abläufe durchgespielt werden: Schon ein Miniprogramm, das fünf verschiedene Folgen von jeweils zwei bis drei Schritten 20 mal wiederholt, hat unüberschaubar viele Abläufe – mehr als 5 hoch 20, also 100 Billionen.
Darüber hinaus sind die von einer Software unterstützten Prozesse – beispielsweise die Buchhaltung einer Firma oder die Steuerung komplexer Maschinen – derart vielfältig, dass die Komponenten für jeden Kunden in immer wieder neuer Weise zusammengesetzt werden müssen (bei vielen Millionen Zeilen Code, zum Beispiel bei SAP R/3, wird diese Varianz praktisch unüberschaubar). Man muss alle Kombinationen der Komponenten in immer wieder neuer Ausführungsumgebung testen. Unter diesen Umständen ist es nahezu ein Wunder, dass die heutige Qualitätssicherung mithilfe intelligent ausgewählter Tests, vieler Reviews und konstruktiver Vorgaben, etwa systematischer Entwicklungsmethoden und -richtlinien, es geschafft hat, die Fehlerrate in 1000 Programmzeilen gegenüber sieben bis 20 Fehlern (im Jahr 1974) auf 0.05 bis 0.2 Fehler in den 1990er Jahren zu senken. Der Verbesserungsrate von 100 in zwei Jahrzehnten steht leider eine Komplexitätssteigerung um den Faktor zehn in fünf Jahren gegenüber.
Die Vision vom „allgegenwärtigen“ (ubiquitous) oder „organischen“ Computing bringt weitere Komplexität mit sich. Mit organischem Computing ist gemeint, dass sich Softwarekomponenten selbstständig immer wieder neu zusammensetzen, jeweils angepasst an die Bedürfnisse der aktuellen Umgebung. Realität ist dies im „mobilen“ Computing schon heute: Eine Komponente passt sich an die in der aktuellen Umgebung verfügbaren Komponenten (Hardware und Software) an, um gemeinsam mit diesen „location-based“-Dienste zu erbringen. Ein Beispiel ist das Bereitstellen aktueller Bilder und Informationen während eines Stadtrundgangs (Informationsportal „Heidelberg mobil“, http://www.heidelberg-mobil.de).
Will man eine derartige Software prüfen, muss der Test während der Ausführung der Software (der so genannten Laufzeit) erfolgen: Sobald sich eine neue Komponente mit einer bereits existierenden verbindet, um gemeinsam einen Dienst zu erbringen, muss getestet werden, ob die neue Komponente passt, ob die gemeinsamen Abläufe also korrekt sind und das Ausführen anderer Dienste dadurch nicht behindert wird. Solche Tests können nicht immer wieder neu von Testern angestoßen werden – die Software muss diese Tests selbst initiieren, auswerten und entsprechend darauf reagieren. Diese Tests sind in der Komponente also eingebaut („built-in“).
In unserem von der Landesstiftung Baden-Württemberg finanzierten Forschungsprojekt MORABIT (Mobile Ressource Adaptive Built-In-Tests, http://www.morabit.org/) entwickelten wir zusammen mit Projektpartnern der „European Media Laboratory Research“ GmbH und der Universität Mannheim eine Infrastruktur und eine Methode für „Built-In-Tests“. Die Idee existiert schon seit den 1990er Jahren, da Built-In-Tests auch „traditionelle“ Tests während der Entwicklung (der so genannten Entwicklungszeit) vereinfachen können. Bisher gab es aber keine umfassende Umsetzung, sondern nur einzelne konzeptuelle Ideen und Teilimplementierungen.
Welche Fragen müssen an Built-In-Tests gestellt werden? Stellen Sie sich dazu folgendes Szenario vor: Eine Bankkomponente wird von anderen Komponenten benutzt, um Geldgeschäfte abzuwickeln, wobei die Bankkomponente selbst eine geeignete Kursumrechnungskomponente verwendet. In der Grafik auf Seite 34 (links) ist die Ausführung dargestellt, ohne dass Tests in das System eingebaut sind: Die Bank ruft während einer Überweisung eine fehlerhafte Umrechnungskomponente auf, die ein falsches Ergebnis liefert. Die Pfeile in der Grafik beschreiben den Nachrichtenaustausch zwischen zwei Komponenten, die Reihenfolge von oben nach unten stellt die zeitliche Reihenfolge der Nachrichten dar.
Die Bank sollte also besser vorher testen, ob die Umrechnungskomponente ihren Erwartungen entspricht. Dazu ist es erforderlich, während der Entwicklungszeit einen Testfall zu definieren, welcher der Bankkomponente eingebaut ist und sie dazu befähigt, der Umrechnungskomponente zu sagen, dass der Testfall nun ausgeführt werden soll. Darüber hinaus muss die Bankkomponente das Ergebnis erhalten, und jemand muss zur Entwicklungszeit definiert haben, was ein richtiges und was ein falsches Ergebnis ist und wie die Bankkomponente auf ein falsches Ergebnis reagieren soll. Das hört sich trivial an, wirft aber viele Forschungsfragen auf. Die Entscheidungen, die Menschen beim Testen zur Entwicklungszeit treffen, müssen formalisiert und in der Software abgebildet werden, um auch während der Laufzeit effizient und effektiv testen zu können.
Erste Fragen sind: Was soll die Bankkomponente testen, welche Testfälle sollen in sie eingebaut werden? Welche Probleme können zwischen zwei Komponenten entstehen? Die übergebenen Informationen könnten beispielsweise unterschiedlich interpretiert werden, sodass die Bank zuerst die Quellwährung angibt, während die Umrechnungskomponente die erste Information als Zielwährung interpretiert. Um dieses Missverständnis zu verhindern, müssten weltweit alle Umrechnungskomponenten standardisiert werden. Oder die Komponenten müssten sich vor der Zusammenarbeit über die Bedeutung der Parameter austauschen. Beides ist für diesen Spezialfall denkbar – nicht aber für jeden beliebigen Dienst.
Es ist also wichtig, dass die Bankkomponente testen kann, welche Eingabe die Umrechnungskomponente erwartet und was sie daraus berechnet. Bei komplexeren Diensten, bei denen die Komponenten untereinander mehrere Nachrichten verschicken, entstehen weitere Probleme durch „Protokollfehler“, das heißt, die Komponenten haben ein unterschiedliches Verständnis der Nachrichtenreihenfolgen. Alle diese Probleme werden während der Entwicklungszeit bei traditionellen Komponenten von „Integrationstests“ überprüft. Dabei sind die zu integrierenden Komponenten bekannt, und die Testfälle können darauf abgestimmt werden. In unserem Beispiel aber muss die Bankkomponente Testfälle dabei haben, die für beliebige Umrechnungskomponenten aussagekräftig sind. Im Projekt MORABIT haben wir Richtlinien zur Auswahl geeigneter Testfälle entwickelt.
Zweite Frage: Nehmen wir an, die Bankkomponente hat geeignete Testfälle eingebaut. Wie kann sie diese zur Ausführung bringen? Beim Entwicklungszeittest versetzt man die zu testende Komponente in einen bestimmten Ausgangszustand, von dem aus eine Überprüfung besonders gut möglich ist. In unserem Fall muss die Bankkomponente den Umrechnungskurs der Umrechnungskomponente festlegen, da es ihr ansonsten nicht möglich ist, die Gültigkeit des Ergebnisses zu überprüfen. Üblicherweise wird eine Umrechnungskomponente diese Möglichkeit nicht haben, sondern stattdessen die Umrechnungskurse an zentraler Stelle, etwa beim internationalen Devisenmarkt, abfragen. Für den Test muss die Umrechnungskomponente also über eine spezielle Testschnittstelle verfügen, die Dienste bereitstellt, die ausschließlich für das Testen benötigt werden – auch hier stellt sich die Frage, was genau diese Dienste leisten sollen. Je mehr Testdienste verfügbar sind, umso mehr verrät die Komponente von ihrem Verhalten. Aus Wettbewerbsgründen wollen Komponentenhersteller jedoch oft nur das Nötigste preisgeben. Zudem besteht die Gefahr, dass Komponenten an der Testschnittstelle korrektes Verhalten zeigen, beim Durchführen des Dienstes aber absichtlich Fehler machen. Es ist also eine Frage des gegenseitigen Vertrauens zwischen den Nutzern und den Herstellern von Komponenten. Letztlich wird der Markt entscheiden, welche Testdienste nachgefragt und angeboten werden. Im Projekt MORABIT haben wir Mechanismen und Richtlinien für eine geeignete Definition von Testschnittstellen entwickelt.
Und noch etwas ist zu klären: Zu welchem Zeitpunkt sollen die Tests angestoßen werden? Sollten die Tests vor jedem neuen Aufruf stattfinden? Das wäre eine hohe zusätzliche Belastung, die gerade bei kleinen, mobilen Komponenten erhebliche Probleme bereiten würde, da deren Ressourcen (Batterie, Speicher) eingeschränkt sind. Hier schlagen wir eine Reihe von typischen Testzeitpunkten vor, beispielsweise die erste Kontaktaufnahme zu einer neuen Komponente oder Zeitpunkte, in denen die zu testende Komponente wenig belastet ist, sodass man Tests vorsorglich ausführen kann.
Dritte Frage: Mithilfe der Testschnittstelle kann die Bankkomponente die mitgebrachten Testfälle anstoßen. Aber wie verhindert man, dass dieser Test nicht die eigentlichen Dienste der Umrechnungskomponente behindert oder gar verfälscht? Ein Beispiel: Hat die Bankkomponente gerade den Umrechnungskurs zu Testzwecken gesetzt und es kommt von dritter Seite eine Umrechnungsanfrage, so wird diese mit dem falschen Kurs berechnet. Selbst wenn der Testablauf nicht die Informationen der eigentlichen Dienstausführung verändert, kann die Antwortzeit für die Dienste durch gleichzeitig laufende Tests stark verlängert werden. Im Projekt MORABIT haben wir Mechanismen erarbeitet, die dafür sorgen, dass Test und Diensterbringung sich nicht gegenseitig behindern. Wir haben darüber hinaus Steuerungsmechanismen verfügbar gemacht, um die Ausführung des Tests an die vorhandenen Ressourcen anzupassen. Ist eine zu testende Komponente gerade durch eine Dienstausführung belastet, werden Tests aufgeschoben oder in kleinere Häppchen aufgeteilt. Diese Mechanismen stellt eine Infrastruktur bereit, die aktiv wird, sobald ein Test zwischen zwei Komponenten ausgeführt werden soll.
Vierte Frage: Die Umrechnungskomponente liefert ein Testergebnis. Was macht die Bankkomponente mit diesem Ergebnis, wenn es nicht mit dem erwarteten Ergebnis übereinstimmt? Zur Entwicklungszeit würde man bei der getesteten Komponente einen Fehler suchen und diesen verbessern. Das ist zur Laufzeit nicht möglich. Hier sind verschiedene Risikostufen zu unterscheiden. Ist der getestete Dienst sehr kritisch, kann es sinnvoll sein, die komplette Software oder zumindest die beteiligten Komponenten außer Betrieb zu nehmen. Stehen noch weitere Komponenten zur Verfügung, wie es im gewählten Beispiel zu erwarten ist, könnte sich die Bankkomponente eine andere Umrechnungskomponente suchen – und dann auch testen; siehe Grafik rechts oben.
Im allgemeinen Fall sind aber auch komplexe Verhaltensanpassungen der testenden Komponente denkbar. Sie könnte einen anderen Algorithmus ausführen, der auf den Dienst der fehlerhaften Komponente verzichtet. Dies wirft prinzipielle Fragestellungen auf: Wer garantiert, dass die komplexen Anpassungsmechanismen, die aufgrund der Testergebnisse ausgeführt wer-den, selbst korrekt und nicht fehlerhaft sind? Inwieweit ist das Verhalten dieser Komponente noch vorhersag-bar? Wir alle wissen, dass Menschen diese Anpassungs-fähigkeit haben – und gerade deshalb ist ihr Verhalten oft nicht vorhersagbar. Software sollte natürlich durch den Menschen steuerbar sein. Es ist also gut zu über- legen, welche Testreaktionen wirklich sinnvoll sind und wie man diese nachvollziehbar und von außen steuerbar machen kann.
Wir konnten im Projekt MORABIT den ersten Prototypen und die erste Methode entwickeln, um oben genannte Fragen größtenteils zu beantworten. Dies bietet ein Experimentierfeld, um die Güte der vielen Heuristiken, beispielsweise die Menge der verfügbaren Testzeitpunkte und Testreaktionen, zu überprüfen. Gleichzeitig wurden viele neue Fragen aufgeworfen: MORABIT hat auf Tests der Dienste fokussiert, kann man diese Ergebnisse auch auf Qualitätseigenschaften wie Performanz oder Sicherheit übertragen?
Gerade im Bereich dieser Eigenschaften ist das Monitoring wirksam, das heißt, man überprüft während der Laufzeit kontinuierlich, ob ein Performanz- oder ein Sicherheitsproblem auftritt, und reagiert erst dann. Wann ist es besser, die Probleme schon durch vorsorgliche Tests aufzudecken? Wann reicht es aus, erst nach Auftreten des Problems aktiv zu werden? Und weiterhin: Wie kann man die Ergebnisse früherer Tests nutzen, um spätere Tests effizienter zu gestalten?
Ein wichtiges Einsatzgebiet der Built-In-Tests ist das mobile Computing. Hier sind die erbrachten Dienste abhängig vom Kontext – es müssen dann auch die verwendeten Testfälle und Testreaktionen kontextabhängig sein, sodass beispielsweise in einem kritischen Nutzungskontext ausführlichere Tests erfolgen können. Wie kann man die heutigen Konzepte für kontextabhängiges Verhalten auf Tests übertragen? Das Erstellen und die Pflege von Testfällen ist sehr aufwendig, berücksichtigt man all diese Möglichkeiten. Kann dies erleichtert werden, wenn man Zwischenbeschreibungen verwendet? Wie sollte eine Werkzeugunterstützung dafür aussehen? Und nicht zuletzt: Wie kann man die bereitgestellten Mechanismen und Konzepte systematisch erproben?
Hilfreich wäre es, automatisch entsprechende Benchmarks zu erzeugen, die jeweils eine Komponentenmenge mit spezifischen Interaktionen umfassen, anhand derer man gezielt die durch die Mechanismen erreichte Qualität messen kann. Es zeigt sich also: Erst wenn die durch Built-In-Tests erreichte Qualität vorhersagbar ist, kann man es auch der Software überlassen, ihre Qualität selbst zu sichern.
Kontakt: paech@informatik.uni-heidelberg.de