Überprüfungsmethoden für Software

Professionelle Fehlersuche

Die wachsende Verbreitung von Computern führt zu einer immer größeren Abhängigkeit von diesen Systemen und der entsprechenden Steuerungssoftware. Gerade wenn hohe Geldbeträge oder gar Menschenleben von einer korrekt arbeitenden Software abhängen, sind Methoden zur effizienten Überprüfung der Software zwingend erforderlich.


Inhalt


Schon Hieronymus stellte treffend fest: "Errare humanum est - Irren ist menschlich". Kein Mensch ist kann Fehler völlig vermeiden und daher sind auch die Ergebnisse der menschliche Arbeit immer mit Fehlern behaftet. Dieses Gesetz trifft ganz besonders auf Softwareprodukte zu, da die immer kürzere Produktzyklen eine komplexe Überprüfung der Software nicht mehr zulassen. Der Käufer einer neuen Version wird meist ungewollt zum Beta-Tester. Es ist oft ratsam auf die Version X.0a, b oder c zu warten. die vermutlich von allen gravierenden Fehlern bereinigt wurde.

Dieses Vorgehen kann vielleicht in einem Textverarbeitungsprogramm gerade noch akzeptiert werden, ist aber bei der Gefährdung von Menschen absolut nicht akzeptabel.


Kosten von Softwarefehlern

Damit Sie sich ein Bild von dem Aufwand machen können, der für die Fehlervermeidung betrieben werden muß, werden zuerst mal ein paar Beispiele aufgegriffen.

Schon in den sechziger Jahren führte ein Programmfehler im Fortran-Code einer Venussonde zum Scheitern der Forschungsmission und einem Verlust von etlichen Millionen Dollar.

Es kann nur vermutet werden, wieviel Millonenverluste der Wirtschaft jährlich entstehen durch mangelhafte Software. Beispiel ist hier der Börsenkollaps im Oktober 1987, bei dem der automatische Aktienhandel durch Computer die Hauptverantwortung trägt. Besonders heikel wird das Thema, wenn Menschen zu Schaden kommen. Am 3.6.1988 ließ beispielsweise der Kommandeur des amerikanischen Kreuzers Vincennes versehentlich einen iranischen Airbus abschießen, was den Tod von 290 Menschen verursachte. Die anschließende Untersuchung zeigte zwar, daß die Werte korrekt angezeigt wurden aber die Benutzerschnittstelle vermutlich nicht besonders gut war und so ein verhängnisvoller Fehler auftreten konnte (Flugzeug im Sinkflug statt Flugzeug im Steigflug).

Auch vom Chiphersteller Intel hört man mal wieder von Startschwierigkeiten bei der Produktion des Pentium Prozessors. Im Vergleich zu den wesentlich heikleren Problemen mit den Vorgängern i386 und i486 handelt es sich beim Pentium um einen vergleichsweise geringe Fehler bei der Division FDIV. Die Genauigkeit der Ergebnisse läßt in den letzten Stellen noch Wünsche offen. Laut Intel ist dies nur für Mathematiker und Techniker von Interesse, aber die Schadenfreude der Hardwarespezialisten kennt kaum Grenzen. Wieviel Pentiumdesigner benötigt man, um eine Glühbirne einzuschrauben ? 1,99998730671276, aber das ist genau genug für Nichttechniker. Das "Intel inside" Werbeschild wird auch als Warnschild bezeichnet oder zu "Errata inside" abgewandelt.


Fehlerursachen

Das Hauptproblem für das Entstehen von Fehlern ist die mit dem Austausch und Verarbeiten von Informationen verbunden, d.h. bei der Kommunikation im weitesten Sinne (Tabelle 1). Hier unterscheidet man im Wesentlichen drei Kommunikationsarten. Die intrapersonale Kommunikation vollzieht sich innerhalb eines Menschens (Denken und Wahrnehmen), die interpersonale Kommunikation beinhaltet den Informationsaustausch zwischen zwei (Gesprächs-)Partnern und schließlich die mediengebundene Kommunikation vollzieht sich im Austausch zwischen kleinen Gruppe von Kommunikatoren (z.B. Journalisten) und einer größeren Gruppe von Rezipienten (z.B. Leser). Die dritte Kommunikationsform kann aber bei der Programmierung vernachlässigt werden.

Fehlerursachen
Kommunikationsproblemeintrapersonale und interpersonale Irrtümer können auftreten
GedächtnisproblemeDer Mensch hat Probleme beim speichern von Informationen, d.h. der Mensch kann nicht wie ein Computer denken
KomplexitätsproblemeGroße, nebenläufige oder Parallele Programme überfordern die inellektuellen Fähigkeiten von Menschen
Mangelndes ProblemverständnisViele Programmierer haben eine unzureichende Kenntnis des Anwendungsgebietetes
Tabelle 1: Durch den Menschen können eine Vielzahl von Fehlern bei der Softwareentwicklung gemacht werden.

Fehler in der intrapersonale Kommunikation können durch Streß, Ermüdung oder mangelnde Motivation auftreten (z.B. statt BRUTTO wird NETTO ein Wert zugewiesen), während auf der anderen Seite bei der zwischenmenschlichen Kommunikation eine Reihe von Fehlerursachen, die sogenannten Irrtümer auftreten können. Im wesentlichen unterscheidet man zwischen Erklärungs-, Übermittlungs-, Entschlüsselungs- und Inhaltsirrtümern. Ein Erklärungsirrtum tritt auf, wenn Sie nicht das sagen, was Sie wirklich meinen. Der Übermittlungsirrtum betrifft Fehler auf dem Übertragungsweg, also wenn beispielsweise Information durch die Sekretärin falsch übermittelt werden. Der Entschlüsselungsirrtum beruht auf der falschen Interpretation der übermittelten Information, also die Gesprächspartner aneinander vorbeireden. Beim Inhaltsirrtum liegen Mißverständnisse über stillschweigende Annahmen vor.

Daneben können noch weitere Fehlerquellen vorliegen. Der Mensch kann nur eine begrenzte Anzahl von Informationen speichern. Ein sogenanntes Gedächtnisprobleme liegt vor beispielsweise vor, wenn Sie an dem Ergebnis einer komplexen Operation interessiert sind, das Sie erst durch manuelles Programmausführung erhalten. Schon bei wenigen Programmverzweigungen und Schleifen werden Sie schnell den Überblick verlieren. Das "Denken wie ein Computer" liegt also außerhalb der intellektuellen Fähigkeiten des Menschens. Auch komplexe Programme mit nebenläufige oder parallele Teilen überfordern schnell die menschlichen Fähigkeiten.

Aber auch das mangelndes Problemverständnis kann eine Fehlerquelle sein. Wenn Sie beispielsweise kein erfahrener Schachspieler sind, werden Sie große Probleme bei der Erstellung eines Schachprogramms haben.


Fehlerbeseitigung

Bevor auf die Fehlerbeseitigung eingegangen werden kann, muß zuerst einmal festgelegt werden was wir unter einem Fehler verstehen. Ein Fehler ist eine Abweichung vom festgelegten Ein- und Ausgabeverhalten des Programms. Liegt "nur" eine Abweichung vom Leistungsverhalten in Bezug auf Zeit- und Speicherplatzbedarf sprechen wir von einem Mangel und alle übrigen Abweichungen, wie etwa Fehler im Handbuch werden als Unangemessenheit bezeichnet.

Programmverifikation
Ist nur für kleine Programmteile anwendbar. Für große Programme ist diese Problem nicht entscheidbar. Aufwand ist sehr hoch und manuelle Beweise können Fehler enthalten. Auch können fehlerhafte Programme nicht als korrekt bewiesen werden
Review
Fehler in der Anforderungsdefinition oder Systemspezifikation sollen aufgedeckt werden. Es wird also frühzeitig das Systemverhalten simuliert. Reviews können Fehler nicht vollständig aufdecken, da Kommunikationsprobleme mit Auftraggeber existieren und keine systematischen Verfahren für große Systeme existieren.
Inspektion
Es wird versucht, ein Programm manuell auf Korrektheit zu überprüfen. Dabei können Täuschungen auftreten.
Testen
Mit Testdaten wird versucht, Fehlverhalten aufzudecken. Unverzichtbare Analysemethode zum Aufdecken von Fehlern.
Tabelle 2: Verfahrenzur Fehlerbeseitigung

Die Fehlerbeseitigung kann auf zwei Arten angegangen werden. Entweder Sie versuchen Fehler von vorn herein auszuschließen oder Sie müssen eine Programmanayse vornehmen mit der die Fehler gefunden werden. Tabelle 2 stellt häufig verwendete Verfahren zur Fehlerbeseitigung im Überblick vor.

Vermeiden

Zur Fehlervermeidung müssen Sie die Fehlerursachen bekämpfen. Die Kommunikation kann in neuen Projekten durch die Einführung von Prototypen gesteigert werden. D.h. Funktionen und Prozeduren werden erst später mit Leben gefüllt. Deshalb liegt schon in frühen Projektphasen ein lauffähiges Programm vor. Darüber hinaus verbessern Schulung in psychologischen und geisteswissenschaftlichen Problemen die Kenntnis der psychologischen Situation und das Wissen von Kenntnis und Aufgaben des Partners.

Das Gedächtnisproblem kann durch Entwicklungswerkzeuge und Methoden verkleinert werden. Beispiele dafür sind syntaxgesteuerte Editoren, umfangreiche Compilermeldungen und höhere Programmiersprachen. Das Verständnis der Hard- und Softwareumgebung kann durch Konfigurationsmanagement mit Projektbibliotheken, ausreichender Zugriff auf Dokumentationen und Qualitätssteigerung der Handbücher erzielt werden. Auch die Motivation der Entwickler kann gesteigert werden, wenn die Bezahlung sich nicht nach Quellcode Zeilen (loc - lines of code) sondern auch nach dem Aufwand für den Test richtet. Das Verständnis eines Problems wird durch die Verwendung von formalen und ausführbaren Spezifikationsmethoden verbessert.

Die vorgestellten Maßnahmen führen zwar zu einer erheblichen Reduzierung der Fehler, aber ausschließen lassen sich diese trotz allem nicht völlig. Daher sind auf jeden Fall noch weitere Maßnahmen notwendig.

Ausmerzen

Größere Softwareprojekte werden in der Regel in mehreren Phasen realisiert (Tabelle 3). In der ersten Phase werden die Anforderungen an das Produkt (Spezifikation) festgelegt. Anschließend wird in der Entwurfsphase die Zerlegung des Problems in einzelne Module vorgenommen, deren Verhalten genau festgelegt wird. Danach wird erst die Implementierung in der Realisierungsphase vorgenommen. Die letzte Phase nimmt dann die Programmwartung ein.

Testen bei der Softwareentwicklung
AnforderungsdefinitionÜberprüfung auf Korrektheit, Vollständigkeit und Konsistenz durch Inspektion und manuelles Durchspielen
EntwurfÜberprüfung auf Korrektheit, Vollständigkeit und Konsistenz durch Inspektion und manuelles Durchspielen
RealisierungÜberprüfen und Ausführen des Codes durch Inspektion, Test oder Verifikation
WartungBeseitigung der Fehler ohne neue zu erzeugen. Erfolgt mit Regressionstests, d.h. alle Test werden nach den änderungen wiederholt.
Tabelle 3: Schon in frühzeitigen Phasen der Softwareentwicklung sollte die Überprüfung des Produkts erfolgen.

Natürlich sollte ein Fehler möglichst früh gefunden und beseitigt werden, da es in der Regel teuer ist Fehler in den ersten beiden Phasen erst bei der Implementierung zu korrigieren. Die Anforderungsdefinition und Spezifikation kann meist nur durch wiederholtes Durchspielen der Auswirkungen (Review) überprüft werden Durch diese Simulation des Systemverhaltens können aber Fehler nicht vollständig ausgeschlossen werden.

Idealerweise wird dann bewiesen, daß das Programm keine Abweichungen gegenüber der Spezifikation hat. Dies ist aber nur in der Theorie machbar, da der Aufwand für diese Beweise immens ist und ein fehlerhaftes Programm nicht als korrekt bewiesen werden kann. Der Beweisversuch liefert aber keine Hinweise zur Fehlerbeseitigung. Außerdem können manche Nachweise auch nicht geführt werden, da im Allgemeinen Schleifen nicht verifiziert werden können (Halteproblem).

Inspektion

Die manuelle Überprüfung des Programmtextes (Inspektion) durch den Menschen bietet neben dem Programmtest noch eine weitere Überprüfungsmethode. Bei der Inspektion müssen Sie aber neben den zu Beginn geschilderten Problemen noch mit dem Problem der Täuschung rechnen. Bei der "Tricktäuschung" weicht der Programmtext von dem allgemein Üblichem ab und erschwert damit das Verständnis. Die Programmierung mit Seiteneffekten ist ein Beispiel für eine solche Täuschung. Die "Erwartungstäuschung" wird durch die Erwartung, mit der Sie an eine Aufgabe gehen gekennzeichnet. Beim Auffinden einer Kommentarzeile geht man normalerweise davon aus, daß der Programmcode auch genau das Verhalten hat, das im Kommentar beschrieben wird. Bei einer "Überdeckungstäuschung" wird ein Fehler durch den überwiegenden Eindruck überdeckt. Beispielsweise wird der Fehler in dem Wort Softwareüberprüfungsmethodan meist überlesen. Auch bei sich wiederholenden Teilen wird oft davon ausgegangen, das gleich Programmteile auch eine ähnliche Wirkung haben. Man spricht dann von einer "Wiederholungstäuschung",


Der Test

Damit bleibt der Test als wichtiges Mittel zur Softwareüberprüfung. Nehmen Sie an, daß ein Programm 20 Fehler enthält. Dann würden in einem "idealen Test" genau 20 Testfälle ausreichen, um mit jedem Fall genau einen Fehler aufzudecken. Leider gibt es für hierfür kein Berechnungsverfahren. Sie könnten aber einfach alle Testfälle nehmen und würden mit diesem "erschöpfenden Test" alle Fehler finden. Leider hilft dieses Vorgehen in der Praxis nur wenig. Wenn Sie beispielsweise alle Testfälle für 3 Variablen a 16 Bit durchspielen wollen, erhalten Sie 2 hoch 16 * 2 hoch 16 * 2 hoch 16 = 2 hoch 48 Testfälle. Wenn Sie jetzt 0,0001 sec Rechenzeit pro Testfall benötigen, dann dauert es immer noch ca. 900 Jahre, um alle Möglichkeiten zu erfassen. Das Testen ist also erschöpfend in mehrerlei Hinsicht.

Der Test kann also nur die Software stichprobenhaft überprüfen und damit gewisse Fehlertypen ausschließen. Das Ziel des Testens ist also die Bestätigung der Qualität einer Software durch planmäßiges Ausführen unter vorher festgelegten Bedingungen. Bei der Festlegung der Testfälle sollten Sie einige Prinzipien beachten (Tabelle 4).

Tabelle 4: Für die Auswahl von Testfällen sollten Testprinzipien eingehalten wrden.

Neben den Testeingaben sollten Sie auch die erwarteten Ausgaben genau festhalten. Die einzelnen Testfälle sollten sowohl ungültige und unerwartete wie auch gültige und erwartete Eingaben berücksichtigen. Teile Sie die Eingabewerte in Gruppen mit ähnlichen Auswirkungen ein. Wählen Sie dann für jede Gruppe mindestens einen Testfall. Noch besser ist die Auswahl von Werten die an den Grenzen der Zahlenbereiche liegen, da diese Extemwerte oft nicht berücksichtigt werden. Gehen Sie nicht von der Annahme aus, daß keine Fehler existieren. Wenn Sie einen Fehler gefunden haben überprüfen Sie die Programmumgebung des Fehlers genauer, denn es hat sich gezeigt, daß Fehler gehäuft auftreten. Außerdem ist es immer besser, wenn Sie nicht Ihre nicht eigene Programme testen sondern dies lieber einem Kollegen überlassen, da viele Programmierer das vom Gefühl her als destruktiven Akt ansehen. Wenn Ihr Programm viele Ausgaben in Text oder Zahlenform hat, sollten die Tests nicht durch Menschen vorgenommen sondern automatisch Prüfmethoden zur Hilfe genommen werden. Zum Schluß sollten Sie ach noch überprüfen, ob Ihr Programm nicht mehr tut als es soll. Ein Zeile der Form

if Name = "Meier" then Überweise_Geld_in_die_Schweiz

kann der Computerkriminalität Tür und Tor öffnen.


Strategien zur Testerstellung

Wenn Sie ein Programm testen wollen, können Sie nach zwei alternative Strategien (Tabelle 5) vorgehen. Beim implementierungsorientierten oder strukturorientiertem Test (White-Box Test) werden die Testdaten durch die interne Struktur des Programms bestimmt. Dagegen werden die Testfälle beim spezifikationsorientierten oder funktionsorientierten Test (Black-Box Test) werden die Testfälle aus einer gründlichen Analyse der Spezifikation abgeleitet. Beide Methoden haben sowohl Vor- wie auch Nachteile, die genauer betrachten werden sollen.
Testansätze
1. implementierungsorientiertes Testen
  • Kontrollflußbezogenes Testen
  • Datenflußbezogenes Testen
2. spezifikationsorientiertes Testen
  • Datenbereichsflußbezogenes Testen
  • Funktionales Testen
3. nicht eindeutige Zuordnung
  • Diversitäres Testen ("Back-to-Back"-Testen)
1. & 2.
  • reproduzierbares Testen
Tabelle 5: Die Testmethoden weden im Wesentlichen in zwei Gruppen eingeteilt

Strukturorientiertes Testen

Grundlage für das strukturorientierte Testen ist meist der Daten- und Kontrollfluß eines Programms (Bild 1). Hierbei wird zuerst versucht, alle denkbaren Wegen durch den Graphen des Programms zu finden. Anschließend werden jedem gefundenen Weg ein oder mehrere Testeingaben zugeordnet, die genau entlang des Weges abgearbeitet werden. Damit die Struktur der einzelnen Anweisungen und vor allem Fallunterscheidungen näher betrachtet werden können wurden eine Reihe von Techniken entwickelt, deren genaue Betrachtung aber den Rahmen dieses Beitrags sprengen würde.

Bild 1: Den Kontrollfluß eines Programms können Sie mit einem Flußdiagramm graphisch darstellen.

Da die Grundlage der Testdaten ein Programm mit einer fest vorgegebenen Syntax und Semantik ist, kann der Test durch eine automatische Unterstützung erleichtert werden. Darüber hinaus können auch Programme getestet werden, für die keine Spezifikation vorliegt. Leider kommt es immer noch häufig vor, daß die Spezifikation nur unvollständig oder in den Köpfen der Entwickler vorhanden ist. Das Problem beim strukturorientierte Testen ist, daß vergessene Teile der Spezifikation nicht gefunden werden. Die Überprüfung der Funktionsweise eines Programms kann nur durch einen funktionsorientierten Test gewährleistet werden. Außerdem sind viele Fragen bei der Testfallermittlung nicht entscheidbar, d.h. Sie können nicht berechnet werden. Beispielsweise kann nicht berechnet werden, ob eine Anweisung jemals ausgeführt wird.

Funktionsorientiertes Testen

Beim funktionsorientierten Testen wird dagegen die interne Programmstruktur nicht berücksichtigt und die Testfälle werden nur aus der Spezifikation ermittelt. Die Qualität der ausgewählten Testdaten ist stark von der Aussagekraft der Spezifikation abhängig. Wenn Sie beispielsweise die Ein- und Ausgabebereiche einer Funktion zwar genau festgelegt haben, aber die Aufgabe dieser Funktion nur grob beschrieben haben, dann können Sie auch nur Testfälle bestimmen, die die Ein- und Ausgabewerte an den Grenzen Ihres Wertebereichs testen. Sie können aber nicht die Arbeitsweise der Funktion genauer testen.

Wenn aber die Spezifikation genügend Aussagekraft hat, können Sie auch die Funktionsweise in den Test einbeziehen. Das Problem bleibt die Frage, ob ein Programm mehr tut als es soll (Computerkriminalität). Dies kann nur mit strukturorientiertem Testen ermittelt werden.


Test großer Programme

Große Programme in einem Durchgang zu testen ist nicht sinnvoll, da Sie zu viele Testfälle zur Abdeckung aller Programmzweige benötigen. Desweiteren ist die Ermittlung noch fehlender Testfälle in der Regel sehr schwierig, da die Eingabedaten bis zum erreichen der Sie interessierenden Programmstelle vielen Veränderungen unterworfen sind. Aber auch die Lokalisierung von Fehlern und die anschließende Beseitigung sind aufgrund der Programmgröße meist sehr schwierig. Schließlich spricht noch ein Argument gegen das Testen großer Programme, daß in einem größeren Erntwicklerteam die Parallelarbeit beim Testen schwierig zu koordinieren ist.

Modultest

Alle diese Nachteile sprechen gegen den "alles auf einmal Test". Daher testen Sie sinnvollerweise jedes Programmteil (Modul) (Bild 2) einzeln (Black-Box oder White-Box). Dies ist aber mit einer Mehraufwand verbunden, der zum Teil erheblich ist, da sie für jeden Modul sogenannte Treiber und Stellvertreter benötigen. Die Treiber rufen den Modul mit den entsprechenden Eingaben auf, während Platzhalter die benutzten Module mit unterschiedlicher Komplexität simulieren. Entweder meldet ein sogenannter "Stub-Modul" lediglich, daß ein Aufruf erfolgte, oder ein konkretes Ergebnis wird zurückgeliefert, mit dem der aufrufende Modul weiterrechnen kann oder es werden korrekte Ausgaben für eine kleine Teilmenge der erlaubten Eingaben zurückgeliefert.

Bild 2: In größeren Programmsystemen rufen sich die Funktionen der einzelnen Module hierarchisch auf.

Big Bang Testen

Das einfachste Vorgehen (nichtinkrementelles oder "big bang"-Testen) (Tabelle 6) ist nun, daß Sie zuerst alle Module einzeln und unabhängig voneinander testen. Danach wird dann erst das gesamte Programm getestet. Dadurch haben Sie den Vorteil, daß in der Modulphase viele parallele Testaktivitäten stattfinden können. Auch hält sich die für den Test benötigte CPU-Zeit noch in Grenzen.

    Teststrategien
  1. alles-auf-einmal
    • bei großen Programmen nicht sinnvoll
  2. nichtinkrementelles Testen/"big bang"-Testen
    • Vorgehen
      1. Teste alle Module einzeln
      2. Teste danach das gesamte Programm
    • Vorteile
      1. viele parallele Testaktivitäten in der Modulphase
      2. weniger CPU-Zeit
    • Nachteile (überwiegen)
      1. hoher Aufwand für Treiber & Stubs
      2. Schnittstellen-Fehler werden spät erkannt
      3. Fehlerlokalisation im gesamten Programm schwierig
  3. inkrementelles Testen
    • Vorgehen
      1. Teste zu Beginn irgendein Modul
      2. Füge Modul hinzu und teste
      3. Wiederhole b) bis ganzes Programm getestet
    • Vorteile
      1. keine Treiber bei topdown, keine Platzhalter bei bottom up
      2. Schnittstellenfehler werden früh erkannt
      3. Fehlerlokalisation einfacher
      4. zuerst gewählten Module werden gründlicher getestet
    • Nachteile
      1. parallele Arbeit bei der Integration der Module kaum möglich
      2. höherer Testaufwand
Tabelle 6: Teststrategieen im Überblick

Sie erkaufen sich die Vorteile aber auf Kosten einiger schwerwiegender Nachteile. Die Erstellung von Treibern und Platzhaltern ist mit einem hohem Aufwand verbunden. Weitere Nachteile sind die späte Erkennung von Schnittstellen-Fehlern und die Fehlerlokalisierung im gesamten Programm gestaltet sich als schwierig.

Inkrementelles Testen

Besser stehen Sie sich, wenn Sie nach dem inkrementellen Prinzip testen. Hierbei wird zu Beginn irgendein Modul getestet und anschließend in jedem Schritt ein Modul zum Testpaket hinzugefügt bis das ganzes Programm getestet wurde. Als Vorteil ist der geringere Aufwand bei der Erstellung von Treibern und Platzhaltern zu nennen. Wenn Sie in der Modulhierarchie bei den unteren Module beginnen, dann benötigen Sie keine Platzhalter und wenn Sie oben in der Hierarchie beginnen, dann benötigen Sie keine Treiber. Durch die frühe Integration einzelner Module werden Schnittstellenfehler früh erkannt. In Gegensatz zur nichtinkrementellen Vorgehensweise ist die Lokalisierung von Fehlern einfacher und die zuerst gewählten Module werden gründlicher getestet. Nachteilhaft wirkt sich der höhere Testaufwand und die geringe parallele Arbeit bei der Integration der Module aus.

    Testen (inkrementelles)
  1. topdown Testen
    • Vorgehen
      1. beginne mit Modul an der Spitze der Hierarchie
      2. füge Modul hinzu, der direkt benutzt wird
    • Vorteile
      1. günstig für Probleme in oberen Modulen
      2. Programmskelett vorhanden
    • Nachteile
      1. Platzhalter müssen erstellt werden
      2. Erzeugung von Testfällen für untere Module schwierig (oder unmögl)
  2. bottom up Testen
    • Vorgehen
      1. beginne mit Modul am Ende der Hierarchie
      2. füge Modul hinzu, der Module des Teilsystems benutzt
    • Vorteile
      1. günstig für Probleme in unteren Modulen
      2. Testbedingungen leichter zu erzeugen, da Treiber das zuletzt dazugebundene Modul aufruft
      3. Überprüfung der Testergebnisse leichter
    • Nachteile
      1. Treibermodule müssen erzeugt werden
      2. kein Programmskelett vorhanden
  3. modifiziertes topdown Testen
    • Vorgehen
      1. Teste alle Module einzeln
      2. topdown Testen
    • Vorteile
      1. Testfälle für untere Module können leicht erstellt werden
    • Nachteile
      1. Jeder Modul benötigt zusätzlich Treiber
  4. vertikales Scheiben Testen
    • Vorgehen
      1. Zerlege Hierarchie in mehrere Teilhierarchien
      2. Teste Teilhierarchien parallel (bel. Schema)
      3. Teste Integration der Teilbäume
    • Vorteile
      1. paralleles Arbeiten besser möglich
    • Nachteile
      1. gemeinsame Module in verschiedenen Hierarchien
      2. Integration am Ende deckt evtl. schere Fehler auf
  5. Sandwich Testen
    • Vorgehen
      1. Bestimme waagerechten Schnitt durch Hierarchie
      2. Teste oberen Teil mit topdown und unteren Teil bottom up Testen
      3. Ersetze Platzhalter des topdown Teils durch die bottom up Teile
    • Vorteile
      1. nützlich bei großen Programmen
      2. Vorteil topdown: Programmskelett
      3. Vorteil bottom up: Test für tiefere Module
    • Nachteile
      1. Module an unteren Ende des topdown Teils schlecht vollst. zu testen
  6. modifiziertes Sandwich Testen
    • Vorgehen
      1. Einzeltest der Module
      2. Sandwich testen
Tabelle 7: Große Programmsysteme sollte man durch modifiziertes Sandwich-Testen überprüfen, für mittlere und kleine Systeme ist die bottom up Methode vorzuziehen.

Wie Sie Tabelle 7 entnehmen können sind eine Reihe von Variationen des inkrementellen Testens denkbar. Beim Topdown-Testen wird der Test mit dem Modul an der Spitze der Aufrufhierarchie begonnen und anschließend ein Modul hinzugefügt, das direkt benutzt wird. Günstig wirkt sich dieses Vorgehen für Probleme in oberen Modulen aus. Weiterhin ist schon sehr früh ein lauffähiges Programmskelett vorhanden. Nachteilhaft wirkt sich aus, daß Platzhalter erstellt werden müssen und die Erzeugung von Testfällen für untere Module schwierig oder unmöglich ist. Das Bottom-up Testen geht genau den umgekehrten Weg. Beginne den Test mit Modul am Ende der Hierarchie und füge dann ein Modul hinzu, der Module des bereits getesteten Teilsystems benutzt. Dieses Vorgehen ist günstig für Probleme in unteren Modulen. Außerdem sind die Testbedingungen leichter zu erzeugen, da der Treiber das zuletzt dazugebundene Modul aufruft. Auch die Überprüfung der Testergebnisse ist in der Regel leichter. Nachteilhaft wirkt sich der Aufwand für die Erzeugung Treibermodulen und das fehlende Programmskelett aus.

Wenn Sie das topdown Testen modifizieren und zuerst alle Module einzeln testen, können die Testfälle für untere Module können leicht erstellt werden. Nachteilhaft wirkt sich aus, daß zusätzlich auch für jeden Modul benötigt Treiber benötigt werden.

Das vertikales Scheiben Testen zerlegt die Modulhierarchie zuerst in mehrere Teilhierarchien, die dann parallel nach einem beliebigen Schema getestet werden. Abschließend wird dann die Integration der Teilbäume in das Gesamtsystem getestet. Der Vorteil ist die verbesserte parallele Arbeit beim Modultest, aber Nachteilhaft können sich gemeinsame Module in verschiedenen Hierarchien auswirken. Auch die Integration am Ende kann eventuell erst spät schwere Fehler aufdecken.

Die Vorteile topdown und des bottom up Verfahrens werden beim Sandwich Testen miteinander kombiniert. Dabei wird zuerst ein waagerechten Schnitt durch Hierarchie gemacht. Danach wird der oberen Teil mit topdown und der untere Teil mit bottom up Testen überprüft. Danach werden die Platzhalter des topdown Teils durch die bottom up Teile ersetzt. Das verfahren ist nützlich beim Test großer Programme. Der Vorteil des topdown Verfahrens ist des frühe Programmskelett und der des bottom up Verfahrens ist der bessere Test für tiefere Module. Als Nachteil bleibt nur der Test für Module am unteren Ende des topdown Teils übrig, die schlecht vollständig zu testen sind.

Als weitere Verbesserung können Sie noch das Sandwich Testen dahingehend modifizieren, daß zuerst ein Einzeltest der Module vorgenommen wird und dann erst das Sandwich Testen angewendet wird.

Große Programmsysteme sollte man durch modifiziertes Sandwich-Testen überprüfen, für mittlere und kleine Systeme ist die bottom up Methode vorzuziehen.


Fehlerlokalisierung

Nachdem Sie nun einen Fehler festgestellt haben, ist die Position des Fehlers meist noch nicht sofort ersichtlich, so daß Sie Maßnahmen zur Fehlerlokalisierung ergreifen müssen. An erster Stelle sind hier die allseits bekannten Verfahren zu nennen, die ich als Holzhammer-Methoden bezeichnen möchte. Der Vorteil der Methoden ist sicherlich, daß nur wenig Überlegen bei der Anwendung notwendig ist. Der Nachteil sind die geringen Erfolgsaussichten. Tabelle 8 stellt diese Methoden im Überblick dar.

Holzhammer-Methoden

An erster Stelle kann hier der Speicherabzug (DUMP) genannt werden. Zwar können Sie den vollständigen Programmzustand protokollieren (letzte Möglichkeit), aber die Nachteile überwiegen. Die Beziehung zwischen Speicherstelle und einer Variablen ist oft problematisch. Durch die immense Datenmengen des statisches Programmabbilds können Fehler leicht übersehen werden oder an der falschen Stelle auftreten. Der Schluß auf die Fehlerursache ist oftmals sehr problematisch.

    Holzhammer-Methoden
  1. Speicherabzug (DUMP)
    • Nachteile
      1. Beziehung Speicherstelle und Variable problematisch
      2. immense Datenmengen
      3. statisches Abbild
      4. Fehler teilweise maskiert
      5. Schluß auf Fehlerursache problematisch
    • Vorteile
      1. vollständiger Programmzustand wird Protokolliert (letzte Möglichkeit)
  2. Druckanweisungen über Programm verstreuen
    • Beachte
      1. einheitliche Ausgaben, z.B. Programmteil, Programmzustand,...
      2. minimale Testdaten, d.h. Ausgaben in Schleifen höchstens 3-mal, Trace für Untermodule/Unterfunktionen kann abgeschaltet werden
      3. sinnvolle Positionen sind Verzweigungen, Ein-/Ausgaben
    • Nachteile
      1. Zufallsmethode
      2. evtl. viele Ausgaben
      3. verleitet zu Programmänderungen(Fehler können maskiert werden, neue Fehler, in zeitkrit. Anwendungen werden Ausführungszeiten verändert
    • Vorteile
      1. Anzeigen von Variablenbwerten
      2. Dynamik des Programmablaufs wird protokolliert
  3. (automatische) Werkzeuge
    • bedingte oder feste Breakpoints
    • Nachteile
      1. Zufallsmethode
      2. unter Umständen immense Datenmengen
      3. Werkzeuge zum Tel wenig effizient
Tabelle 8: Holzhammer Methoden regen nicht zum Denken an und sollten nur angewandt werden, wenn alle anderen Methoden versagen.

Eine auch sehr beliebte Methode zur Felerlokalisierung ist es, Druckanweisungen über das Programm zu verstreuen. Sie sollten dann aber auf einheitliche Ausgaben, wie z.B. Programmteil, Programmzustand, usw., minimale Testdaten, d.h. Ausgaben in Schleifen höchstens 3-mal, Trace für Untermodule/Unterfunktionen kann abgeschaltet werden und auf sinnvolle Positionen (Verzweigungen, Ein-/Ausgaben) achten.

Vorteilhaft ist zwar das Anzeigen von Variablenwerten die Protokollierung der Programmdynamik, aber Druckanweisungen bleiben eine Zufallsmethode, die unter Umständen sehr viele Ausgaben erzeugt. Sie werden auch zu voreiligen Programmänderungen verleitet, die eventuell neue Fehler hervorrufen. Darüber werden in zeitkritischen Anwendungen die Ausführungszeiten verändert.

Auch automatische Werkzeuge, wie z.B. Debugger bleiben immer noch eine Zufallsmethode, Erzeugen unter Umständen große Ausgaben oder können die Laufzeit Ihrer Programme verändern.

Als Fazit bleibt: Holzhammer Methoden regen nicht zum Denken an und sollten nur angewandt werden, wenn alle anderen Methoden versagen.

Denkmodelle

Wesentlich besser zur Fehlersuche sind die verschiedenen Denkmodelle (Tabelle 9). Die Induktion schließt vom Allgemeinen auf das Besondere, während die Deduktion den umgekehrten Weg beschreitet. Bei der Induktion suchen Sie Hinweise zu Fehlersymptomen in Testläufen (das Besondere). Danach stellen Sie die Verbindung zwischen Symptomen her (das Allgemeine) indem Sie eine Spezifikation bzw. Klassifikation der Fehler anhand einer Tabelle vornehmen. Die Tabelle enthält Kennzeichen was, wann, wo, in welchem Umfang ist und nicht ist. Damit können Sie den Fehler meist lokalisieren.

Beim deduktiven Vorgehen stellen Sie zuerst allgemeine Theorien/Prämissen aufstellen (das Allgemeine). Danach eliminieren Sie Möglichkeiten und ziehen einen Schluß ziehen für vorliegenden Fall (das Besondere).

Nur für kleine Programme können Sie auch mit dem Rückwärts-Trace weiterkommen. Beginnen Sie an dem Punkt, an dem falsches Ergebnis sichtbar wurde. Bestimme Sie Werte von Programmvariablen an dem Punkt. Lassen Sie das Programm "in Gedanken" rückwärts laufen. Vergleichen Sie den gedanklichen Programmzustand mit ist-Werten. Der Fehler liegt dann bei Zustandsabweichungen vor.

Denkmodelle

    Induktion
  1. Hinweise zu Fehlersymptomen in Testläufen suchen (das Besondere)
  2. Verbindung zwischen Symptomen herstellen (das Allgemeine) Spezifikation/Klassifikation Tabelle mit Kennzeichen: was, wann, wo, in welchem Umf. und 2 Spalten ist, ist nicht
  3. Fehler wird damit meistens lokalisiert
    Deduktion
  1. allgemeine Theorien/Prämissen aufstellen (das Allgemeine)
  2. Eliminieren von Möglichkeiten
  3. Schluß ziehen für vorliegenden Fall (das Besondere)
    Rükwärts-Trace (nur für kleine Programme)
  1. Beginne an Punkt, an dem falsches Ergebnis sichtbar
  2. Bestimme Werte von Programmvariablen an dem Punkt
  3. Programm in Gedanken rückwärts laufen lassen
  4. Vergleiche mit gedanklichen Programmzustand mit ist-Werten
  5. Fehler bei Zustandsabweichungen
Tabelle 9: Denkmodelle bilden den besten Ansatz zur Fehlerlokalisierung

Prinzipien für die Programmierung

Damit Fehler schnell gefunden werden, sollten Sie einige Prinzipien bei der Programmierung beachten (Tabelle 10). Programmieren Sie testbar und verwenden z.B. verständliche Variablennamen. Rechnen Sie schon bei der Programmerstellung mit Fehlern und bauen schon Diagnosehilfen ein. Bei Fehlern sollten Sie zuerst gründlich nachdenken und Probleme überschlafen oder mit Kollegen diskutieren. Benutzen Sie Werkzeuge nur als zusätzliche Hilfsmittel. Experimente sind nur als letztes Hilfsmittel erlaubt. Bei Problemen sollten Sie versuchen die fehlerhaften Programmteile zu isolieren und allgemein mit kleinen Modulen arbeiten. Hilfen für den Test sind Listen mit Variablen und Konstanten. Debug-Anweisungen sollten extra gekennzeichnet werden. Beherzigen Sie grundlegende Verwaltungs- bzw. Management-Prinzipien. Ordnen Sie Ihre Ausgaben und werfen Sie unnötigen Output weg, aber behalten alte Programmversionen.
    Prinzipien zur Lokalisierung
  1. Codiere testbar, z.B. verständliche Variablennamen
  2. Einbauen von Diagnosehilfen ( es wird wahrscheinlich Fehler geben)
  3. Vorgehen
    • Denke
    • bei Problemen, Überschlafen, Kollegen fragen
    • Werkzeuge nur als zusätzliche Hilfsmittel
    • Experimente nur als letztes Hilfsmittel
    • isoliere Probleme
    • bearbeite kleine Module
  4. Testhilfen
    • Liste von Variablen & Konstanten
    • Kennzeichne Debug-Anweisungen
  5. Verwaltungs-/Management-Prinzipien
    • Ordne Ausgaben
    • Wirf unnötigen Output weg, aber behalte alte Programmversionen
Tabelle 10: Vorgehen zur Lokalisierung von Fehlern

Korrekturprinzipien

Bei der Korrektur von Fehlern sollten Sie die nachfolgenden Regeln beherzigen:
  1. In der Umgebung eines großen Fehlers sind meist weitere Fehler zu vermuten
  2. Beheben Sie die Ursache und nicht Symptome
  3. Testen Sie Fehlerkorrekturen strenger, besonders in großen Programmen.
  4. Nach Fehlerkorrekturen sind Regressionstests notwendig, damit nicht neue Fehler als Nebeneffekt erzeugt werden.
  5. Behandeln Sie die Fehlerkorrektur wie den Programmentwurf.
  6. Ändern Sie Programm nur in der Quellsprache.

Literatur


Ralf Glogau

Zurück zur Startseite

© 1996, Interware Network Services, Ralf Glogau
ralf [AT] interware.de