Suchen

Software-Test Fehlersuche mit der Code-Coverage-Analyse

| Autor / Redakteur: Iris Steinbacher und Matthias Stumpf* / Martina Hafner

Die strukturorientierten Testverfahren zählen zu den dynamischen Methoden, basieren also auf der Ausführung von Software-Code. Da sie aber keine Regeln für die Erzeugung von Testfällen vorgeben, ist es wichtig auf eine entsprechende Abdeckung der Struktur zu achten.

Firmen zum Thema

( Archiv: Vogel Business Media )

Bei der Entwicklung der Rakete Ariane 5 wurden Teile der Programmierung von deren Vorgängermodell übernommen. Da die Software bis dahin fehlerfrei funktionierte, ging man davon aus, dass dies auch bei der neuen Rakete der Fall sein würde. Auf Flug 501 im Jahr 1996 folgte dann die Ernüchterung, denn 39 Sekunden nach dem Start sprengte sich die Ariane selbst. Wie konnte das passieren?

Die Horizontalgeschwindigkeit des Nachfolgermodells war rund fünfmal höher und ihre Umwandlung in eine ganze Zahl, die der übernommene Programmteil vornehmen sollte, führte zu einem Überlauf, der nicht abgefangen wurde. Die Folge: Der Referenzrechner, der ebenfalls von dem Problem betroffen war, schickte Diagnosedaten zum Hauptrechner und schaltete sich ab. Nachdem der Hauptrechner diese Daten nun fälschlicherweise als Flugbahndaten interpretierte, gab er unsinnige Steuerbefehle an die Triebwerke, um die errechnete, aber nicht vorhandene Flugabweichung von über 20 Grad zu korrigieren. Die Rakete drohte auseinanderzubrechen und machte sich daraufhin selbst den Gar aus.

Testverfahren für mehr Softwarequalität

Ob Rakete, Flugzeug oder Auto – jedes neue Modell erhält immer mehr Leistung und zusätzliche Funktionen, die von einer Software gesteuert werden. Damit dem Neuwagen nun nicht eine ähnliche Katastrophe wie der Ariane 5 widerfährt, wenden Tier-1-Zulieferer diverse Verfahren zum Testen sicherheitskritischer Software an. Bereits einfache Programme benötigen für einen vollständigen Test enorm viele Testfälle. Ein angemessener Test ist daher immer nur eine Stichprobe, die nur die Anwesenheit, nicht aber die Abwesenheit von Fehlern nachweist.

Tabelle 1: Vor- und Nachteile von White- und Blackbox-Testverfahren (Archiv: Vogel Business Media)

Um die Qualität von Software zu steigern, können diverse Testmaßnahmen ergriffen werden, z.B. Black- und Whitebox-Tests. Beim Blackbox-Test wird nicht der genaue Aufbau des Codes betrachtet, sondern überprüft, ob er mit seiner Spezifikation übereinstimmt. Beim Whitebox-Test hingegen ist der Quellcode bekannt und es wird getestet, ob alle internen Ausführungspfade korrekt sind. Zu seinen Methoden zählen analytische, die erst zur Anwendung kommen, nachdem der Test erstellt wurde, sowie konstruktive, die bereits bei seiner Erstellung vorliegen.

Übersicht dynamischer Testverfahren (Archiv: Vogel Business Media)

Die analytischen Maßnahmen teilen sich in dynamische und statische, wobei dynamische die Ausführung der Software erfordern. Unter die dynamischen Testverfahren fallen unter anderem strukturorientierte und kontrollflussorientierte Methoden.

Vier Arten von Überdeckungstests

(Archiv: Vogel Business Media)

Strukturorientierte Testverfahren geben keine Regel für die Erzeugung von Testfällen an. Deshalb ist es besonders wichtig, auf eine entsprechende Abdeckung (Coverage) der Struktur zu achten. Hierfür gibt es vier Arten, die jeweils eine unterschiedliche Anzahl von Testfällen erfordern: den Anweisungsüberdeckungstest (C0, Statement Coverage), den Zweigüberdeckungstest (C1, Decision- oder Branch Coverage), den Pfadüberdeckungstest (C2, Path Coverage) und den Bedingungsüberdeckungstest (C3, Condition Coverage).

CO: Der Anweisungsüberdeckungstest

Beim C0-Test wird überprüft, ob jede Anweisung einmal durchlaufen wurde, wodurch bei einer 100%igen Anweisungsüberdeckung sichergestellt wird, dass kein unausgeführter Code im Programm existiert. Allerdings wird der Zusammenhang von Codezeilen, wie bei if-/else-Anweisungen, nicht beachtet, weil bei einem Testdurchlauf nur eine davon berücksichtigt werden kann. Trotz einer Anweisungsüberdeckung von 100%, könnte hier ein Fehler übersehen worden sein, wie folgendes Beispiel zeigt:

/* z wird das Doppelte des größeren Werts von x oder y zugewiesen */

int z = x;

if (y > x)

z = y;

z *= 2;

Solange y größer als x ist, genügt ein einziger Testfall, damit die Anweisungsüberdeckung bei 100% liegt. Andernfalls wird die Anweisung z = y nie ausgeführt.

Ein weiterer Nachteil dieser am einfachsten zu implementierenden Testmethode ist, dass sie nicht den Fall überprüft, ob die Abbruchsbedingung einer Schleife je erreicht wird. Eine 100%ige Anweisungsüberdeckung schließt nicht die vollständige Zweigüberdeckung ein.

C1: Der Zweigüberdeckungstest

Tabelle 2: Zusammenfassung der Testfälle C0, C1 und C2 (Archiv: Vogel Business Media)

Deshalb bedient man sich des C1-Tests, denn mit Decision Coverage lässt sich leicht feststellen, ob bestimmte Zweige im Code ungetestet sind, da bei einer Fallunterscheidung beide Fälle, true und false, abgedeckt werden. Für das oben genannte Beispiel wären nun zwei Testdurchläufe nötig, um eine 100%ige Zweig-abdeckung zu erreichen. Allerdings stößt auch dieser Test an seine Grenzen. Nämlich genau dann, wenn Zweige nicht primitiv, also von einander abhängig sind. Das Zweigüberdeckungsmaß bezieht sich daher immer auf den Quotienten aus der Anzahl der ausgeführten primitiven Zweige zur Anzahl aller vorhandenen primitiven Zweige.

C2: Pfadüberdeckungstest

Um nun nicht nur die Verzweigungen an einzelnen Entscheidungspunkten, sondern mögliche Ausführungspfade vom Start- bis zum Endknoten einer Funktion zu überprüfen, wird der C2-Test angewandt. Bei der vollständigen Path Coverage (C2a) werden alle möglichen Pfade getestet, was sich gerade bei Programmen mit Schleifen als unmöglich darstellt. Deshalb werden bei den C2b-Tests (Boundary-Interior-Pfadüberdeckungstest) die Schleifendurchläufe auf <=2 reduziert.

Für den Boundary-Test bedeutet dies, dass jede Schleife keinmal und genau einmal betreten wird. Beim Interior-Test gilt das Schleifeninnere als getestet, wenn alle Pfade abgearbeitet wurden, nachdem sie zweimal durchlaufen wurden. Auf eine natürliche Zahl n begrenzt der C2c-Test (strukturierter Pfadüberdeckungstest) die Schleifendurchläufe. Obwohl die Fehlererkennungsrate beim Pfadüberdeckungstest sehr hoch ist, testet auch er zusammengesetzte, hierarchische Bedingungen nicht ausreichend.

C3: Überprüfung hierarchischer Bedingungen

Tabelle 3: Zusammenfassung des Testfalls C3 (Archiv: Vogel Business Media)

Hierarchische Bedingungen überprüft der C3-Test. Bei der Einfachbedingung C3a wird jede atomare Bedingung jeweils mit true und false getestet, der Mehrfachbedingungsüberdeckungstest C3b betrachtet alle atomaren Bedingungen einer Bedingung und der minimale Mehrfachbedingungsüberdeckungstest C3c evaluiert jede Bedingung zu true und false. Beispiel:

boolean a,b;

if (a || b)

{

...

}

Für den C3a-Test ergeben sich hierfür zwei Testfälle, der Test C3b erfordert vier (22) Testfälle, denn für n atomare Bedingungen werden 2n Kombinationen gebildet. Verfügt eine Programmiersprache über eine so genannte short circuit evaluation, wie beispielsweiseC/C++, werden beim C3c-Test einigeBedingungen unvollständig ausgeführt:

if (a && b)

{

...

}

else

{

lese b aus

}

Ist a false, spielt die Belegung der Variable b keine Rolle. Ist b z.B null, kommt es zu einem Fehler im else-Zweig. Im Test C3c wird die logische Struktur berücksichtigt und der C1-Test ist vollständig enthalten. Ferner ist der C3c-Test berechenbar. In der Praxis ist er allerdings schwer umzusetzen, denn die Anzahl der Ausführungspfade wächst mit der Anzahl an logischen Bedingungen exponentiell und damit auch die Testfälle. Hinzu kommt, dass durch Einschränkungen im Code viele Kombinationen ausgeschlossen sind.

Übersicht dynamischer Testverfahren (Archiv: Vogel Business Media)

Für die Fehlersuche unter Echtzeitbedingungen und die Aufzeichnung der Coverage-Daten stehen Analyse-Tools wie In-circuit-Emulatoren zur Verfügung. Andere Testwerkzeuge können gleichzeitig für Host/Host- bzw. Host/Target-Tests verwendet werden. In beiden Fällen wird der zu testende Source Code instrumentiert und soweit manipuliert, dass das reale Verhalten der Applikation verfälscht wird. Dabei arbeiten die Analyse-Werkzeuge optimaler Weise in Verbindung mit einer statischen Analyse, losgelöst von der Zielhardware (Host/Host), bzw. führen die zu testende Anwendung in ihrer Zielumgebung (Host/Target) aus. Ein Beispiel für den kombinierten Host/Target-Test sind die Werkzeuge der Firmen LDRA und iSYSTEM.

Code Coverage hilft aufzuzeigen, wie gründlich der Source Code abgearbeitet wurde, sagt aber nichts über die Korrektheit des Codes aus. Auch ist sie ein zusätzlicher Indikator für den Fortschritt der Verifikation: Eine niedrige Abdeckung bedeutet, dass die Verifikation nicht abgeschlossen wurde. Allerdings ist der Umkehrschluss falsch.

*Iris Steinbacher ist freie Redakteurin. Matthias Stumpf ist Key Account Manager beim Spezialisten für Entwicklungs- und Test-Tools iSystem. Kontakt: matthias.stumpf@isystem.com

(ID:248210)