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?
(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 ‘<=’).
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.
Stand: 08.12.2025
Es ist für uns eine Selbstverständlichkeit, dass wir verantwortungsvoll mit Ihren personenbezogenen Daten umgehen. Sofern wir personenbezogene Daten von Ihnen erheben, verarbeiten wir diese unter Beachtung der geltenden Datenschutzvorschriften. Detaillierte Informationen finden Sie in unserer Datenschutzerklärung.
Einwilligung in die Verwendung von Daten zu Werbezwecken
Ich bin damit einverstanden, dass die Vogel Communications Group GmbH & Co. KG, Max-Planckstr. 7-9, 97082 Würzburg einschließlich aller mit ihr im Sinne der §§ 15 ff. AktG verbundenen Unternehmen (im weiteren: Vogel Communications Group) meine E-Mail-Adresse für die Zusendung von redaktionellen Newslettern nutzt. Auflistungen der jeweils zugehörigen Unternehmen können hier abgerufen werden.
Der Newsletterinhalt erstreckt sich dabei auf Produkte und Dienstleistungen aller zuvor genannten Unternehmen, darunter beispielsweise Fachzeitschriften und Fachbücher, Veranstaltungen und Messen sowie veranstaltungsbezogene Produkte und Dienstleistungen, Print- und Digital-Mediaangebote und Services wie weitere (redaktionelle) Newsletter, Gewinnspiele, Lead-Kampagnen, Marktforschung im Online- und Offline-Bereich, fachspezifische Webportale und E-Learning-Angebote. Wenn auch meine persönliche Telefonnummer erhoben wurde, darf diese für die Unterbreitung von Angeboten der vorgenannten Produkte und Dienstleistungen der vorgenannten Unternehmen und Marktforschung genutzt werden.
Meine Einwilligung umfasst zudem die Verarbeitung meiner E-Mail-Adresse und Telefonnummer für den Datenabgleich zu Marketingzwecken mit ausgewählten Werbepartnern wie z.B. LinkedIN, Google und Meta. Hierfür darf die Vogel Communications Group die genannten Daten gehasht an Werbepartner übermitteln, die diese Daten dann nutzen, um feststellen zu können, ob ich ebenfalls Mitglied auf den besagten Werbepartnerportalen bin. Die Vogel Communications Group nutzt diese Funktion zu Zwecken des Retargeting (Upselling, Crossselling und Kundenbindung), der Generierung von sog. Lookalike Audiences zur Neukundengewinnung und als Ausschlussgrundlage für laufende Werbekampagnen. Weitere Informationen kann ich dem Abschnitt „Datenabgleich zu Marketingzwecken“ in der Datenschutzerklärung entnehmen.
Falls ich im Internet auf Portalen der Vogel Communications Group einschließlich deren mit ihr im Sinne der §§ 15 ff. AktG verbundenen Unternehmen geschützte Inhalte abrufe, muss ich mich mit weiteren Daten für den Zugang zu diesen Inhalten registrieren. Im Gegenzug für diesen gebührenlosen Zugang zu redaktionellen Inhalten dürfen meine Daten im Sinne dieser Einwilligung für die hier genannten Zwecke verwendet werden. Dies gilt nicht für den Datenabgleich zu Marketingzwecken.
Recht auf Widerruf
Mir ist bewusst, dass ich diese Einwilligung jederzeit für die Zukunft widerrufen kann. Durch meinen Widerruf wird die Rechtmäßigkeit der aufgrund meiner Einwilligung bis zum Widerruf erfolgten Verarbeitung nicht berührt. Um meinen Widerruf zu erklären, kann ich als eine Möglichkeit das unter https://contact.vogel.de abrufbare Kontaktformular nutzen. Sofern ich einzelne von mir abonnierte Newsletter nicht mehr erhalten möchte, kann ich darüber hinaus auch den am Ende eines Newsletters eingebundenen Abmeldelink anklicken. Weitere Informationen zu meinem Widerrufsrecht und dessen Ausübung sowie zu den Folgen meines Widerrufs finde ich in der Datenschutzerklärung, Abschnitt Redaktionelle Newsletter.
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.
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.