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

Anbieter zum Thema

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?

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.

Artikelfiles und Artikellinks

(ID:45686267)