Sind Ihre Testfälle gut genug? Was Testfallgüte für die Fehlerfindung bedeutet

Autor / Redakteur: Frank Büchner* / Sebastian Gerstl

Auch ein „gutaussehender“ Satz von Testfällen, der zudem 100% Code-Überdeckung erreicht, kann Defekte in der Software übersehen. Nur „gute” Testfälle decken Fehler auf. Aber wie findet man solche Testfälle?

Anbieter zum Thema

100% Code Coverage beim Testen von Software ist noch lange keine Garantie für fehlerfreien Programmcode. Wie legt man saubere Testfälle an, mit denen keine Bugs übersehen werden?
100% Code Coverage beim Testen von Software ist noch lange keine Garantie für fehlerfreien Programmcode. Wie legt man saubere Testfälle an, mit denen keine Bugs übersehen werden?
(Bild: Clipdealer)

Die Unit-Test-Spezifikation für ein einfaches Testobjekt könnte wie folgt aussehen: Ein Startwert und eine Länge definieren einen Bereich. Bestimme, ob ein weiterer Wert in diesem Bereich liegt oder nicht. Das Ende des Bereichs soll nicht zu Bereich gehören. Alle Werte sind ganzzahlig.

Das Testobjekt besteht die drei Testfälle wie in Bild 1 gezeigt, die zusammen 100% Codeüberdeckung (und zwar MC/DC) erreichen. Trotzdem haben sie eine Schwachstelle. Das Problem liegt darin, dass nicht alle Anforderungen getestet wurden. Insbesondere gibt es keinen Testfall, der prüft, ob das Ende des Bereichs wirklich nicht zum Bereich zählt. Man kann auch sagen, wir haben ohne Grenzwerte getestet. Ein Testfall mit den Werten 5, 2 und 7 für Start, Länge und Wert würde aufgrund eines Defekts in der Software fehlschlagen. Das liegt an einem falschen relationalen Operator in einer Entscheidung (‘<’ anstelle von ‘<=’).

Bildergalerie
Bildergalerie mit 8 Bildern

Grenzwerte, Min/Max, Extreme, Illegale

Grenzwerte

Wie viele Testfälle mit welchen Daten benötigen wir, um die Grenzen ausreichend zu testen? Betrachten wir als Beispiel die (Teil-) Spezifikation „Eingangswert kleiner als 5”. Wie könnte dies implementiert werden und welche Tests entdecken fehlerhafte Implementierungen?

Die in Bild 2 aufgeführte Tabelle zeigt in der ersten Spalte mögliche Implementierungen für „Eingangswert kleiner als 5”. Die beiden ersten Zeilen sind korrekte Implementierungen, alle anderen Zeilen enthalten fehlerhafte Implementierungen. In der zweiten Spalte ist angegeben, wie wahrscheinlich es ist, dass (a) ein solcher Defekt implementiert wird und dass (b) ein implementierter Defekt bei einem Code-Review übersehen wird. Zur Ermittlung der ersten Wahrscheinlichkeit wird die Anzahl der unterschiedlichen Zeichen zwischen der korrekten und der fehlerhaften Implementierung herangezogen; für die zweite Wahrscheinlichkeit der optische Unterschied.

Beispielsweise besteht zwischen der korrekten Implementierung (i<5) und der fehlerhaften Implementierung (i>5) nur ein Unterschied (der relationale Operator ist falsch) und dieser Unterschied ist optisch unauffällig. Deshalb ist ein solcher Defekt als wahrscheinlich (likely) eingestuft. Im Gegensatz dazu muss man für die fehlerhafte Implementierung (i!=5) zwei falsche Zeichen verwenden und es besteht ein großer optischer Unterschied. Deshalb ist ein solcher Defekt als unwahrscheinlich (unlikely) eingestuft.

Die drei weiteren Spalten in der Tabelle geben das Ergebnis der jeweiligen Implementierung für die Eingabewerte 4, 5 und 6 an. Dabei ist der Wert 5 der Grenzwert aus der Spezifikation „Eingangswert kleiner als 5” und 4 bzw. 6 repräsentieren Grenzwert-1 und Grenzwert+1. Die Ergebnisse in der Tabelle, die in fetter Schrift dargestellt und die auch rot unterlegt sind, sind falsch. Das bedeutet, dass diese Testfälle die fehlerhafte Implementierung aufdecken. Die Frage ist nun, ob zwei Testfälle mit den Eingangswerten 4 und 5 ausreichend viele fehlerhafte Implementierungen aufdecken oder ob drei Testfälle nötig sind.

Die zwei Testfälle mit 4 und 5 decken nicht die beiden fehlerhaften Implementierungen (i!=5) und (i<>5) auf. Diese beiden fehlerhaften Implementierungen sind jeweils der Test auf Ungleichheit, ausgedrückt in unterschiedlichen Programmiersprachen. Der Eingabewert 6 (d.h. ein dritter Testfall) würde die fehlerhaften Implementierungen aufdecken. Die fehlerhafte Implementierung (i<>5) ist als wahrscheinlich eingestuft; andererseits werden Programmiersprachen, in denen ‚<>‘ als Ungleich-Operator gebraucht wird, in eingebetteten System kaum eingesetzt. Die fehlerhafte Implementierung (i!=5) ist als unwahrscheinlich eingestuft. Insgesamt brauchen wir deswegen meines Erachtens den Testfall mit dem Wert 6 nicht unbedingt.

Ein spezieller Fall ist die fehlerhafte Implementierung (i==4), die durch keinen der drei Eingangswerte aufgedeckt wird. Das schätze ich aber nicht als kritisch ein, weil (i==4) in zweierlei Hinsicht falsch ist: Der relationale Operator und der Wert sind beide offensichtlich falsch, das sollte bei einem Review sofort auffallen. Soll die fehlerhafte Implementierung jedoch durch Testen aufgedeckt werden, brauchen wir einen weiteren Testfall, beispielsweise mit dem Wert 3. Bei diesem Eingangswert ist das erwartete Ergebnis 1, aber das tatsächliche Ergebnis ist 0, womit die fehlerhafte Implementierung aufgedeckt ist. Daraus könnten wir nun schlussfolgern, dass wir vier Testfälle brauchen, um auf der sicheren Seie zu sein, insbesondere wenn kein Code-Review vorgesehen ist.

Jetzt Newsletter abonnieren

Verpassen Sie nicht unsere besten Inhalte

Mit Klick auf „Newsletter abonnieren“ erkläre ich mich mit der Verarbeitung und Nutzung meiner Daten gemäß Einwilligungserklärung (bitte aufklappen für Details) einverstanden und akzeptiere die Nutzungsbedingungen. Weitere Informationen finde ich in unserer Datenschutzerklärung.

Aufklappen für Details zu Ihrer Einwilligung

Vier Testfälle werden von René Tuinhout vorgeschlagen ([1] und [4]); er nennt dies Black Box Boundary Value Analysis (B3VA). Aber: Wir dürfen annehmen, dass der Bereich „kleiner als 5“ eine untere (linke) Grenze hat. Im schlimmsten Fall ist das INT_MIN. Und wir dürfen weiterhin annehmen, dass auch diese Grenze geprüft wird, d.h. es wird einen Testfall mit diesem Grenzwert als Eingabe geben. Dieser Testfall würde die fehlerhafte Implementierung (i==4) aufdecken.

Anmerkung: Das Fragezeichen bei der Implementierung (i<=4) in der zweiten Zeile rührt daher, dass diese Implementierung zwar korrekt ist, aber nicht die Anforderung wiederspiegelt. Deshalb wird ein Reviewer höchstwahrscheinlich bei dieser Implementierung stutzen. Und falls in der Implementierung mit der ‚4‘ ein Programmierfehler passiert, beispielsweise fälschlicherweise (i==4) implementiert wird, wird dies durch die Black-Box-Testfälle mit 4, 5 und 6 nicht aufgedeckt.

Schlussfolgerung: In eingebetteten Systemen (mit „!=“ als Ungleich-Operator) können wir zwei Testfälle (mit den Werten 4 und 5) als ausreichend ansehen, vorausgesetzt Reviews werden durchgeführt und wenn die Anzahl der Testfälle möglichst gering gehalten werden soll. Andererseits braucht man in manchen Fällen vier Testfälle, um alle fehlerhaften Implementierungen aufzudecken.

Für Testdienstleistungen durch Hitex gilt die Vorschrift, mit drei Werten (in vorliegenden Fall 4, 5 und 6) zu testen.

Min/Max-Werte

Wir können den größten möglichen Eingabewert und den kleinsten möglichen Eingabewert (d.h. den „negativsten” Eingabewert) als spezielle Fälle von Grenzwerten betrachten. Deshalb sollten wir auch mit solchen Eingabewerten testen. Das folgende Beispiel (aus [1]) zeigt, dass dies sinnvoll ist.

Die Funktion abs_short() in Bild 3 arbeitet korrekt für Eingangswerte wie -5, 0, 5; diese drei Eingangswerte ergeben korrekt 5, 0, 5. Diese drei Testfälle erreichen auch 100% Code-Überdeckung. Aber der Eingangswert -32768, der kleinste („negativste“) Wert für eine vorzeichenbehaftetet 16-bit-Zahl, ergibt nicht +32768, sondern -32768 (also wieder den Eingangswert). Das liegt daran, dass der korrekte Wert nicht mit 16 Bit dargestellt werden kann. (Hintergrund: -32768 = 0x8000. 0x8000 - 1 = 0x7FFF. Der invertierte Wert ist 0x8000, also derselbe Wert, mit dem wir gestartet sind.)

Extreme Werte

Extreme (oder außergewöhnliche) Eingabewerte sind nicht direkt Grenzwerte oder Min/Max-Werte, sondern speziell in anderer Hinsicht. Betrachten wir als Beispiel eine Minimum-Funktion, die als Eingabe drei ganze Zahlen (a, b und c) ohne Vorzeichen hat und die als Ergebnis den kleinsten der Eingabewerte zurückliefert.

In der Tabelle in Bild 4 sind beinahe ein Dutzend bestandene Testfälle für eine Minimum-Funktion dargestellt. Die ersten drei Testfälle prüfen, dass der kleinste Wert korrekt ermittelt wird, egal bei welcher Eingangsvariablen er auftritt. In den weiteren Testfällen werden Minimum-/Maximum-Werte verwendet. Außerdem werden durch dieses Testfälle 100% Code-Überdeckung erreicht.

Was ist nun mangelhaft an diesem Testfallsatz? Nun, es werden keine extremen oder außergewöhnlichen Eingangswerte verwendet. Beispielsweise kommt kein Testfall vor, bei dem alle drei Werte gleich und positiv sind, etwa (3, 3, 3). Wenn wir diesen Testfall ausführen würden, wäre das Ergebnis fälschlicherweise 0 (und nicht wie erwartet 3). Der Testfall (3, 3, 3) deckt also einen Defekt in der Software auf.

Für eine Sortierfunktion sind extreme oder außergewöhnlich Testfälle beispielsweise, wenn die zu sortierenden Werte bereits sortiert sind, sie in umgekehrter Reihenfolge sortiert sind, wenn alle Werte gleich sind oder wenn es gar keine Werte zu sortieren gibt.

Illegale Werte

Wenn wir das einführende einfache Beispiel betrachten, so sagt die Spezifikation „Alle Werte sind ganzzahlig.” Das gilt somit auch für die Länge des Bereichs. Aber ist ein negativer Längenwert ein gültiger Eingabewert? Wahrscheinlich nicht. Auf jeden Fall ist es aber spannend, einen Testfall mit dem Wert 5 als Startwert und dem Wert -2 für die Länge durchzuführen. Wird der Wert 4 als im Bereich liegend erkannt?

Als Faustregel können wir formulieren: Immer nach (möglicherweise) illegalen Eingabewerten suchen und Testfälle mit diesen Werten durchführen.

Methoden zur Bestimmung der Testfallgüte

Äquivalenzklassenbildung

Ein ständiges Problem bei der Testfallerstellung ist, dass eine Eingangsvariable zu viele mögliche Werte annehmen kann und es ist praktisch unmöglich, alle diese Werte in Testfällen zu benutzen, insbesondere wenn diese Werte noch mit allen möglichen Werten anderer Eingangsvariablen kombiniert werden sollen (kombinatorische Explosion). Die Bildung von Äquivalenzklassen adressiert dieses Problem.

Bildergalerie
Bildergalerie mit 8 Bildern

Äquivalenzklassenbildung ordnet jedem möglichen Eingangswert eine Klasse zu. Die Klassen werden dabei so gebildet, dass alle Werte in einer Klasse als äquivalent für den Test betrachtet werden. Äquivalent für den Test heißt hier, dass man davon ausgeht, falls ein Wert aus einer bestimmten Klassen einen bestimmten Fehler aufdeckt, dies auch alle anderen Werte in dieser Klasse tun. Unter dieser Annahme kann man einen beliebigen Wert aus einer Klasse als Stellvertreter für alle anderen Werte in dieser Klasse betrachten. Damit hat man die Werte, die ein bestimmter Eingangswert annehmen kann, u.U. beträchtlich reduziert und somit die Kombination mit anderen Werten praktikabel gemacht. Ein Beispiel ist Bild 6 zu entnehmen.

Wir müssen uns aber darüber im Klaren sein, dass ein Fehler bei der Äquivalenzklassenbildung dazu führen kann, dass eben nicht alle Werte in einer Klasse äquivalent für den Test sind und falls überdies für den Test derjenige Wert gewählt wird, der einen bestimmten Fehler nicht aufdeckt, obwohl er es sollte, schlüpft dieser Fehler durch. Es liegt in der Verantwortung des menschlichen Äquivalenzklassenbildners, die Klassen sorgfältig zu bilden.

Die Klassifikationsbaummethode

Die Klassifikationsbaummethode (KBM) ist eine Methode zur Testfallspezifikation, welche die Methoden anwendet, die wir bis jetzt kennengelernt haben.

Die Klassifikationsbaummethode beginnt mit der Analyse der Anforderungen. Daraus ergibt sich, welche relevanten Eingaben es gibt. Relevante Eingaben sind solche, die man während der Tests variieren möchte. Nun macht man sich Gedanken über die Werte, die eine relevante Eingabe annehmen kann. Sind es zu viele Werte, bildet man Klassen nach der Äquivalenzklassenmethode. Dann berücksichtigt man Grenzwerte, illegale und extreme Eingabewerte.

Daraus ergibt sich der sogenannte Klassifikationsbaum. Er bildet den oberen Teil einer Testfallspezifikation nach der Klassifikationsbaummethode. Die Wurzel des Baums ist oben, der Baum wächst von oben nach unten, die Blätter des Baums bilden den Kopf der Kombinationstabelle. Die Kombinationstabelle ist der untere Teil einer Testfallspezifikation nach der Klassifikationsbaummethode. Jede Zeile stellt einen Testfall dar. Aus welchen Klassen Werte in einem Testfall verwendet werden, wird durch Markierungen auf den Linien festgelegt. Sowohl das Entwerfen des Baums als auch die Entscheidung über die Anzahl der Testfälle sowie das Setzen der Markierungen in den Zeilen sind menschliche Aufgaben (und deshalb leider auch dem menschlichen Irrtum unterworfen).

Bild 7 ist ein Beispiel für eine Testfallspezifikation nach Klassifikationsbaummethode (KBM). Die Wurzel des Baums ist mit „Suspension“ bezeichnet, d.h. das Testobjekt scheint die Aufhängung eines Kraftfahrzeugs zu sein. Für ihren Test werden zwei (testrelevante) Aspekte betrachtet, nämlich Geschwindigkeit („speed“) und Lenkwinkel („Steering angle“). Beides sind Klassifikationen; diese werden in rechteckigen Rahmen dargestellt. Beide Klassifikationen sind in Äquivalenzklassen eingeteilt. Äquivalenzklassen werden ohne Rahmen dargestellt. Für „Steering Angle” gibt es drei Äquivalenzklassen: „left”, „central” und „right”. Aus dem Klassifikationsbaum können wir nicht erkennen, wie die Werte in einer bestimmten Klasse codiert sind. Das ist abhängig von der Implementierung und interessiert im Rahmen der Klassifikationsbaummethode nicht, da diese auf einem Black-Box-Ansatz basiert. Deshalb ist die Testfallspezifikation nach der Klassifikationsbaummethode abstrakt.

Wenn man nun nicht „central” als extremen Lenkwinkel betrachtet, gibt es für Lenkwinkel keine Grenz-, Extrem- bzw. illegalen Werte. Bei „speed“ ist die anders. Die Klassifikation „speed“ ist in die zwei Äquivalenzklassen „valid“ und „invalid“ aufgeteilt. Letztere Klasse garantiert, dass beim Test ungültige Werte für „speed“ verwendet werden, weil jede Klasse im Baum mindestens einmal für einen Testfall ausgewählt werden muss. Die Klasse „invalid“ ist weiter unterteilt nach der Klassifikation „Too low or too high?”. Das ergibt die zusätzlichen Klassen „negative” und „> v_max”. Testfälle mit Werten aus diesen Klassen finden heraus, was passiert, wenn das Unerwartete / Unmögliche eintritt. Die gültigen Geschwindigkeiten sind in „normal” und „extreme” Geschwindigkeiten eingeteilt. Wir können annehmen, dass die Klasse „zero” für eine gültige Geschwindigkeit nur einen einzigen Wert (wahrscheinlich den Wert 0) enthält, ebenso wie die Klasse „v_max“ (die wahrscheinlich den maximalen Wert aus den Anforderungen enthält). Dies sind Grenzwerte.

Die Kombinationstabelle (der untere Teil der obigen Abbildung) besteht aus sieben Zeilen und spezifiziert somit sieben Testfälle. Die Testfälle können benannt sein. Die Markierungen in jeder Zeile geben an, aus welchen Klassen für diesen Testfall Werte ausgewählt werden. Dies ergibt die (abstrakte) Testfallspezifikation, die den Zweck eines Testfalls anzeigt. Im vorliegenden Beispiel wird dies auch durch den Namen des Testfalls ausgedrückt, aber dies muss nicht unbedingt immer der Fall sein.

Aus der Gesamt-Testfallspezifikation erkennt man, dass es nur drei „normale“ Testfälle gibt (die ersten drei Testfälle). Man kann auch erkennen, dass es beispielsweise einen „normalen“ Testfall mit geringer Geschwindigkeit und Lenkwinkel nach rechts nicht gibt. Gegebenenfalls kann man weitere „normale“ Testfälle hinzufügen. Allerdings geht es hier nicht um die Frage, ob drei normale Testfälle ausreichend sind oder nicht, sondern der Punkt ist, dass es offensichtlich ist, dass es nur drei normale Testfälle gibt. Das ist ein wichtiger Vorteil der Klassifikationsbaummethode.

Das Unit-Test-Werkzeug TESSY enthält einen graphischen Editor für Klassifikationsbäume. Somit können Unit-Tests für TESSY komfortabel anhand der Klassifikationsbaummethode spezifiziert werden.

Methoden zur Ableitung von Testfällen

Empfehlungen aus ISO 26262

Bild 8: Methoden aus ISO 26262 um Testfälle abzuleiten.
Bild 8: Methoden aus ISO 26262 um Testfälle abzuleiten.
(Bild: Hitex)

In Teil 6, Abschnitt 9, listet die ISO 26262:2011 in Tabelle 11 die Methoden zur Ableitung von Testfällen für den Software-Unit-Test auf [7; vgl auch Bild 8]. Der Grad der Empfehlung einer Methode hängt vom Automotive Safety Integrity Level (ASIL) ab. Der ASIL geht von A bis D, wobei D die höchsten Ansprüche bzgl. der Risikoreduktion stellt. Methoden, die besonders empfohlen sind, sind mit einem doppelten Pluszeichen („++”) versehen; Methoden, die empfohlen sind, haben ein einfaches Pluszeichen („+”).

Methode 1a aus Tabelle 11 erfordert, dass die Testfälle aus den Anforderungen abgeleitet werden. Dies ist besonders empfohlen für alle ASIL. Zunächst die Anforderungen zu betrachten, um die Testfälle abzuleiten, ist der naive Ansatz.

Methode 1b aus Tabelle 11 erfordert, dass Äquivalenzklassen verwendet werden, um Testfälle abzuleiten. Das ist empfohlen für ASIL A und besonders empfohlen für ASIL B bis D.

Methode 1c aus Tabelle 11 erfordert, dass Grenzwerte betrachtet werden, um Testfälle abzuleiten. Das ist empfohlen für ASIL A und besonders empfohlen für ASIL B bis D.

Methode 1d aus Tabelle 11 erfordert, dass „Fehler raten“ eingesetzt wird, um Testfälle abzuleiten. Das ist empfohlen für ASIL A bis D.

Die Methoden 1a, 1b und 1c wurden bereits in den vorhergehenden Abschnitten diskutiert. Die Methode 1d wird im Folgenden erläutert.

Fehler raten (error guessing)

Fehler raten (error guessing) erfordert üblicherweise einen erfahrenen Tester, der aufgrund seiner Erfahrung in der Lage ist, fehler-sensitive („spannende”) Testfälle zu finden. Fehler raten wird deshalb auch als erfahrungsbasiertes Testen bezeichnet. Fehler raten ist eine unsystematische Methode, um Testfälle zu spezifizieren (was die ersten drei Methoden nicht sind). Zugegeben, wenn man Checklisten oder Fehlerreports verwendet, kann auch Fehler raten eine gewisse Systematik erhalten. Fehler raten hat einen Bezug zu Grenz-, Extrem- und illegalen Werten, denn Testfälle, die durch Fehler raten entstanden sind, enthalten oftmals solche Werte.

Alternativen

Dieser Abschnitt diskutiert weitere Methoden um an Testfälle zu kommen, die bisher nicht betrachtet wurden.

Testfälle aus dem Quell-Code ableiten

Es ist verlockend, ein Werkzeug zu benutzen, um aus dem Quell-Code automatisiert Testfälle zu erzeugen, beispielsweise mit dem Ziel, 100% Codeüberdeckung zu erreichen. Es gibt hierzu unterschiedliche technische Ansätze, beispielsweise Backtracking oder genetische Algorithmen. Sowohl frei verfügbare Werkzeuge als auch kommerzielle Werkzeuge bieten diese Möglichkeit zur Testfallerzeugung. Wieso sollte man dies nicht im großen Stil verwenden? Nun, es gibt mindestens zwei Aspekte, derer man sich bewusste sein sollte:

  • 1. Auslassungen: Es werden keine Auslassungen im Code gefunden. Lautet eine Anforderung beispielsweise „wenn der erste Parameter gleich dem zweiten Parameter ist, soll eine bestimmte Fehlernummer zurückgegeben werden“ und die Implementierung dieser Anforderung fehlt, dann wird dieser fehlende Code niemals durch Testfälle erkannt werden, die aus dem Code abgeleitet sind. Man benötigt Testfälle, die aus den Anforderungen abgeleitet sind, um nicht-implementierte Funktionalität zu finden.
  • 2. Korrektheit: Man kann nicht aufgrund des Codes entscheiden, ob er korrekt ist oder nicht. Beispielsweise weiß man nicht, ob die Entscheidung (i<5) oder (i<=5) die beabsichtigte Funktionalität implementiert. Dazu benötigt man wiederum Testfälle, die aus den Anforderungen abgeleitet wurden oder ein Testorakel. Ein Testorakel ist eine Instanz, die zu einem Satz von Eingangswerten entscheiden kann, ob das Ergebnis korrekt ist oder nicht.

Deshalb ist es nicht ausreichend, nur Testfälle zu verwenden, die aus dem Quell-Code abgeleitet wurden. Man benötigt auch auf jeden Fall Testfälle, die aus den Anforderungen abgeleitet werden. Aber wäre es nicht eine gute Idee, die Hauptarbeit der Testfallerstellung durch ein Werkzeug erledigen zu lassen? Anschließend kann man ja manuell prüfen, ob auch die Anforderungen getestet wurden und wenn nicht, entsprechend nachbessern.

In einer Studie [5] wurde versucht, genau diese Frage zu beantworten. Die Studie kam zu den folgenden vier Hauptaussagen:

  • 1. Automatisch generierte Testfälle erzeugen eine höhere Codeüberdeckung als manuell erzeugte Testfälle.
  • 2. Automatisch generierte Testfälle führen nicht zu einer Entdeckung von mehr Fehlern.
  • 3. Automatisch generierte Testfälle haben einen negativen Effekt auf die Fähigkeit, das beabsichtigte Verhalten des Codes (von Klassen) zu verstehen.
  • 4. Automatisch generierte Testfälle führen nicht unbedingt zu besseren Werten beim Mutationstest.

Die Studie verwendete das Werkzeug EvoSuite, das automatisch Tests für Java-Klassen erzeugt. Es war eine empirische Studie, in der einhundert Studenten Fehler in Java-Code finden sollten. Die Hälfte der Studenten begann die Suche mit Testfällen, die von EvoSuite generiert waren; die andere Hälfte startete mit eigenen, selbst aus den Anforderungen abgeleiteten Testfällen.

Die Schlussfolgerung aus der Studie ist für mich, dass automatisierte Testfallerstellung keinen Vorteil bringt (beispielsweise weniger Aufwand oder mehr gefundene Fehler); andererseits ist die automatisierte Testfallerstellung auch kein Nachteil. Diese Grundaussage „kein Vorteil / kein Nachteil“ ist in meinen Augen überraschend.

Natürlich kann man die Randbedingungen der Studie diskutieren (z.B. die verwendete Programmiersprache, die Kenntnisse der Studenten, etc.) und überlegen, ob sie auch auf die eingebettete Software-Entwicklung zutreffen.

Zufällige Testdaten / Fuzzing

Wie die Ableitung von Testfällen aus dem Quellcode ist es ebenfalls verlockend, zufällige erzeugte Testeingangsdaten zu verwenden. Mit automatisierter Testausführung kann man in kurzer Zeit viele Testfälle durchführen. Aber: Ein (funktionaler) Testfall benötigt ein erwartetes Ergebnis! Und ohne Testorakel (siehe oben) kann es sehr aufwendig werden, für jeden Testfall zu prüfen, ob das erhaltene Ergebnis korrekt ist oder nicht.

Eine sinnvolle Anwendung von zufällig erzeugten Testdaten gibt es allerdings: Wenn man „alten“, betriebsbewährten Code (sog. „legacy code“), zu dem es vielleicht nie eine Spezifikation gab, portieren oder optimieren muss, dann ist es nützlich, zuvor Tests mit zufällig erzeugten Testdaten auszuführen, die Ergebnisse aufzuzeichnen, und dieselben Test danach zu wiederholen. Erhält man identische Ergebnisse, kann man mit einer gewissen Sicherheit annehmen, dass die Arbeit am alten Code erfolgreich war. Ansonsten betrachte ich Test mit zufälligen Eingangsdaten ohne Ergebnisprüfung als eine Art von Robustheitstest. Durch Robustheitstests wird lediglich grobes Fehlverhalten (z.B. Absturz) festgestellt.

Allerdings kann diese Art von Tests zur Aufdeckung von Safety- und Security-Schwachstellen führen. Die Methode, ein Testobjekt mit syntaktisch richtigen, aber inhaltlich zufälligen Testdaten zu testen, wird auch „Fuzzing“ genannt.

Mutationstest

Wie wir in den vorhergehenden Abschnitten gesehen haben, garantiert 100% Codeüberdeckung nicht die Qualität der Testfälle.

Aber wie kann man die Qualität der Testfälle bewerten? Eine Möglichkeit sind Mutationstests (in IEC 61508 [8] werden diese Tests „error seeding“ oder „Fehlereinpflanzung“ genannt). Wenn man einen Satz von bestandenen Testfällen hat, mutiert man den Code und führt die Testfälle erneut aus. Mutieren bedeutet, den Code semantisch zu verändern, aber so, dass er syntaktisch korrekt bleibt. Beispielsweise kann man eine Entscheidung von (i<5) zu (i<=5) ändern oder man könnte in einer Entscheidung ein logisches ODER durch ein logisches UND ersetzen. Die Frage ist nun, ob der Satz der vorhandenen Testfälle diese Veränderung im Code aufdeckt, d.h. ob mindestens einer dieser Testfälle fehlschlägt. Aus der Zahl der entdeckten bzw. übersehenen Mutationen kann man auf die Qualität der Testfälle schließen.

Fazit

Wie wir gesehen haben, bedeuten 100% Codeüberdeckung nicht automatisch gute Testfälle. Man braucht aber gute Testfälle, um Fehler im Code zu finden. Die Aufgabe, gute Testfälle zu erstellen, ist eine schwierige (hauptsächlich menschliche) Aufgabe; der Einsatz von Werkzeugen ist mit Vorsicht zu genießen.

Der Autor

Frank Büchner ist Principal Engineer Software Quality bei der Hitex GmbH in Karlsruhe.
Frank Büchner ist Principal Engineer Software Quality bei der Hitex GmbH in Karlsruhe.
(Bild: Hitex)

* Frank Büchner hat ein Diplom in Informatik von der Technischen Hochschule Karlsruhe, heute KIT. Seit mehreren Jahren widmet er sich dem Thema Testen und Software-Qualität. Seine Kenntnisse vermittelt er regelmäßig durch Vorträge und Fachartikel. Momentan arbeitet er als „Principal Engineer Software Quality“ bei der Fa. Hitex GmbH in Karlsruhe.

Literaturverzeichnis

[1] Grünfelder, Stephan: Software-Test für Embedded Systems, 2. Auflage, dpunkt.verlag GmbH, Heidelberg, 2017.

{2] Spillner, Andreas, et al: Basiswissten Softwaretest, 5. Auflage, dpunkt.verlag GmbH, Heidelberg, 2012.

[3] Liggesmeyer, Peter: Software-Qualität, 2. Auflage, Spectrum Akademischer Verlag, Heidelberg, 2009.

[4] Tuinhout, René: The Software Testing Fallacy, Testing Experience 02/2008.

[5] Fraser, Gordon, et al: Dos automated Unit Test Generation Really Help Software Testers? A controlled Empirical Study, ACM Transactions on Software Engineering and Methodology, Vol. 24, No. 4, Article 23, published August 2015.

[6] http://www.hitex.de/tessy: Mehr über das Unit-Test-Werkzeug TESSY

[7] ISO 26262, International Standard, Road vehicles – Functional Safety, First edition, 2011

[8] IEC 61508, Functional safety of electrical/electronic/programmable electronic safety-related systems, part 7, IEC, 2000

(ID:45686267)