Überdeckungsmetriken zur Bestimmung der Testgüte (Whitebox-Testing)

https://bildung.social/@oerinformatik/111382882816978614

https://oer-informatik.de/codecoverage

Whitebox-Test, Testabdeckung, Code Coverage, Anweisungsüberdeckung, Zweigüberdeckung, Pfadüberdeckung, Bedingungsüberdeckung, Kontrollflussgraph, JaCoCo, Softwaretest

tl/dr; (ca. 43 min Lesezeit): Dieser Artikel erklärt die Berechnung der einzelnen Whitebox-Testmetriken, nennt die Vorteile und Nachteile von Anweisungsüberdeckung, Zweigüberdeckung, Pfadüberdeckung, Bedingungsüberdeckung. Die praktische Relevanz (die Erzeugung neuer Testfälle) wird an einem Beispiel vorgestellt. Die theoretischen Grundlagen werden mit Javaquelltext und Kontrollflussgraphen dargestellt. Außerdem zeigt der Artikel, wie Du alle Pfade eines Algorithmus notierst, die Mindestzahl an Testfällen bestimmst und begründet eine passende Überdeckungsart auswählst; die englischen Fachbegriffe sind am Ende zusammengestellt [AI-Assisted]. (Zuletzt geändert am 28.10.2026)

Dieser Artikel gehört zu einer Reihe über Softwaretests: Blackbox-Tests / Whitebox-Tests und Überdeckungsmaße / Zyklomatische Komplexität. Zum Üben gibt es Leitfragen zur Whitebox-Systematik, die Übungen ShippingCost, Rabatt und Sortierung sowie Übungsaufgaben.

Schon fit in diesem Thema? Nutze die Kann-Liste und die Leitfragen unter dem Artikel als Checkliste, ob Du die relevanten Kompetenzen bereits hast.

Dieser Artikel baut auf der Kann-Liste der Artikel Unit-Tests und Blackbox-Tests auf.

Als Grundlagen zu Whitebox-Tests und Überdeckungsmaßen kann ich nach Durchlesen des Artikels…

  • … beschreiben, was einen Whitebox-Test auszeichnet und wie er sich vom Blackbox-Test unterscheidet.

  • … den Begriff Testabdeckung bezogen auf die Anweisungen eines Quelltextes erläutern und für gegebene Testfälle den Überdeckungsgrad als Bruch und in Prozent bestimmen.

  • … Anweisungs-, Zweig- und Pfadüberdeckung erläutern, auch anhand englischer Fachtexte (statement, branch, path coverage), und erklären, nach welchen Kriterien jeweils die Testdaten gewählt werden und wie die Maße aufeinander aufbauen.

  • … für gegebene Testdaten bestimmen, welche Anweisungen und Zweige nicht durchlaufen werden, und die Testdaten so anpassen oder ergänzen, dass eine vollständige Überdeckung erreicht wird.

  • … alle Pfade eines Algorithmus ohne Schleifen als Folge von Bedingungen und Anweisungen notieren.

  • … die Mindestanzahl an Testfällen für eine vollständige Anweisungs-, Zweig- oder Pfadüberdeckung bestimmen, auch bei Schleifen und Rekursion.

  • … für einen Algorithmus eine geeignete Überdeckungsart auswählen und die Auswahl begründen.

Darüber hinaus kann ich als Expertenwissen zu Überdeckungsmaßen…

  • … Bedingungs- und Mehrfachbedingungsüberdeckung erläutern, Testdaten dafür bestimmen und erklären, warum eine einfache Bedingungsüberdeckung keine Zweigüberdeckung garantiert.

  • … aus Werten einer Überdeckungsmetrik rückschließen, was daraus für die anderen Überdeckungsmetriken folgt.

  • … erklären, warum Werkzeuge wie JaCoCo für dieselbe Methode andere Werte berechnen als eine Zählung im Kontrollflussgraphen.

  • … begründen, warum eine hohe Testabdeckung keine Fehlerfreiheit garantiert.

Wird ein vorhandenes Programm getestet, so gibt die Tatsache, ob ein Test scheitert oder bestanden wird, für sich genommen noch kein Maß über die Qualität einer Software an. Vielmehr ist es die Güte des Tests selbst, die entscheidet, wie aussagekräftig dessen Ergebnis ist.

Um die Güte der Testfälle selbst messbar zu machen (und somit deren Qualität zu quantifizieren), kann ein Maß bestimmt werden, das angibt, wie viele Aspekte des Programmcodes durch die Tests erfasst werden.

Die Grafik zeigt vorab, welche der im Artikel vorgestellten Überdeckungsmaße welche anderen einschließen:

Überdeckungsmaße im Überblick: Pfad- und Mehrfachbedingungsüberdeckung schließen Zweigüberdeckung ein, diese schließt Anweisungsüberdeckung ein; einfache Bedingungsüberdeckung garantiert keine Zweigüberdeckung (mit KI erstellt)

Was zeichnet einen Whitebox-Test aus?

Bei einem Whitebox-Test (auch Glassbox- oder Strukturtest) leitest Du die Testfälle aus der inneren Struktur des Testobjekts ab, also aus dem Quelltext und seinem Kontrollfluss. Du kennst den Code und wählst die Testdaten so, dass bestimmte Anweisungen, Zweige oder Pfade durchlaufen werden. Beim Blackbox-Test entstehen die Testfälle dagegen allein aus der Spezifikation (Anforderungen, Äquivalenzklassen, Grenzwerte), ohne Blick in den Code.

Whitebox-Tests schreiben meist die Entwickler*innen selbst, in der Regel als automatisierte Unit-Tests. Sie zeigen, welche Teile des Codes noch gar nicht getestet sind, finden nicht erreichbaren („toten“) Code und helfen, Fehler genau zu lokalisieren. Fehlt dagegen eine Anforderung im Code ganz, kann ein Whitebox-Test das nicht bemerken – dafür braucht es die Blackbox-Sicht. Beide Verfahren ergänzen sich.

Wie gründlich ein Whitebox-Test ist, misst man mit Überdeckungsmaßen (Testabdeckung, englisch code coverage oder test coverage). Der Lehrplan „Certified Tester Foundation Level“ des ISTQB (Version 4.0) behandelt davon den Anweisungstest und den Zweigtest; Bedingungs- und Pfadüberdeckung gehören zum weiterführenden Wissen.

Grundlegende Berechnung der Überdeckungsgrade

Die Güte eines White-Box-Tests wird mit Hilfe eines Überdeckungsgrads C ausgedrückt, der angibt, wie viel Prozent der theoretisch möglichen Aspekte durch den Test erfasst werden.

C = \frac{\sum{Anzahl\:getesteter\:Aspekte}}{\sum{Anzahl\:aller\: Aspekte}}

Bezogen auf die Anweisungen eines Quelltextes bedeutet Testabdeckung also: Wie viel Prozent aller Anweisungen werden von den Testfällen mindestens einmal ausgeführt? Führen die Tests z. B. 8 von 10 Anweisungen aus, beträgt die Testabdeckung 80 %.

Dieser Überdeckungsgrad kann für unterschiedliche Aspekte berechnet werden:

  • Wie viele Prozent der Codezeilen durchlaufen meine Testfälle?

  • Wie viele Prozent der Anweisungen durchlaufen meine Testfälle?

  • Wie viele Prozent der Programmzweige durchlaufen meine Testfälle?

  • Wie viele Prozent der Programmpfade durchlaufen meine Testfälle?

  • Wie viele Prozent der (Teil-)Bedingungen werden von meinen Testfällen jeweils erfüllt und verletzt?

Im Idealfall erhält man für einzelne Aspekte mit den vorhandenen Testfällen eine Überdeckung von 100% - beispielsweise führen die Testfälle alle vorhandenen Anweisungen einmal aus.

Ein hoher Überdeckungsgrad allein sagt noch nichts über die Fehlerfreiheit des Codes aus. Es werden so jedoch Bereiche aufgedeckt, die bisher noch nicht getestet oder gar nicht erreichbar waren. Die Ergänzung von Testfällen über eine Überdeckungsmethodik sollte daher nur als Ergänzung zur Äquivalenzklassenbildung und Grenzwertanalyse der Blackbox-Tests gesehen werden und stellt für sich genommen keine hinreichende Testart dar.

An einem Codebeispiel

Die einzelnen Überdeckungsgrade sollen an einer Methode zur Berechnung von Rabatten dargestellt werden.

Es werden Rabatte errechnet in Abhängigkeit der Positionskosten (über 100 Euro -> 5 Euro Rabatt) sowie der Gesamtkosten (über 1000 Euro -> 5% Rabatt auf die rabattierte Gesamtsumme):

public static int rabattAusgeben(int[] kosten){
        int rabatt = 0;
        int summe = 0;
        for(int position:kosten){
            if (position > 100) {
                rabatt += 5;
            }
            summe += position;
        }
        if (summe-rabatt > 1000){
            rabatt +=(int)((summe-rabatt)*0.05);
        }
        return rabatt;
    }

Zum Verständnis der Überdeckung ist es oft hilfreich, statt des Quelltextes den Kontrollflussgraphen zu betrachten. Im Kontrollflussgraphen werden alle Anweisungen/Operationen als Knoten (nodes) erfasst, alle Übergänge und Verzweigungen als Kanten (edges).

Für unser Ausgangsbeispiel sieht ein noch nicht vereinfachter Kontrollflussgraph z.B. so aus:

Kontrollflussgraph der Rabattfunktion

Der Graph umfasst neben dem Knoten und der Kante zur Kennzeichnung von Start/Stop insgesamt:

  • 10 Anweisungen (mit Zahlen 1-10 gekennzeichnet)

  • 12 Kanten (mit Kleinbuchstaben a-l gekennzeichnet)

Zur Übersicht sind hier die einzelnen Operationen und Verzweigungsbedingungen noch genannt - in Kontrollflussgraphen kann dies aber weggelassen werden. Darüber hinaus können Programmsequenzen (z.B. die Anweisungen 1 und 2) zusammengefasst werden, da hier der Kontrollfluss geradlinig und alternativlos ist. Um die Überdeckungszahlen synchron zum Quelltext zu halten, wurde auf diese Vereinfachung verzichtet.

Zeilenüberdeckung

Diese Metrik beschreibt den Anteil der vom Test ausgeführten Programmzeilen im Verhältnis zu allen Programmzeilen. Unser Beispielcode hat 9 ausführbare Zeilen (ohne Methodenkopf und Zeilen, die nur Klammern enthalten).

Wir wollen diese Metrik bestimmen für den folgenden Test: als Vorbedingung wird ein Array mit einem Element (Wert: 100) übergeben und wir prüfen, ob das Ergebnis wie erwartet 0 ist. Mit jUnit wird dieser Test folgendermaßen formuliert:

@Test
public void rabattAusgeben_100_Test() {
    // given: Preparation
    int[] kosten = { 100 };

    // when: Execution
    int result = App.rabattAusgeben(kosten);

    // then: Verification
    int expected = 0;
    assertEquals(expected, result);
}

Abgekürzt identifizieren wir ihn fortan mit einer an Java angelehnten Notation nach dem Stil assertEquals(expected, result):

assertEquals(0, rabattAusgeben({100}))          //Testfallbezeichner, Pseudojava

Dieser Test führt folgende Zeilen aus:

Quellcode, in dem zwei Zeilen nicht markiert sind

Sieben von neun Zeilen des Programms führt unser Test aus – der Zeilenüberdeckungsgrad des Tests beträgt also rund 78 %. Nicht ausgeführt wird der innere Codeblock der beiden if-Anweisungen.

C = \frac{\sum{Anzahl \: getesteter \: Codezeilen} }{\sum{Anzahl \:aller \: Codezeilen}} = \frac{7}{9} \approx 77{,}8\,\%

Wenn wir den Quellcode aber umformatieren, ändert sich bei gleicher Zählung der Wert:

Quellcode mit fehlenden Zeilenumbrüchen bei den if-Statements

Der Quelltext hat nur noch sechs Zeilen, von denen alle ausgeführt werden:

C = \frac{\sum{Anzahl \: getesteter \: Codezeilen}}{\sum{Anzahl \:aller \: Codezeilen}} = \frac{6}{6} = 100\%

Plötzlich haben wir 100% Zeilenüberdeckungsgrad. Diese Metrik scheint also von sehr begrenzter Aussagekraft.

Ihr Vorteil: Debugger und Coverage-Tools liefern in der Regel die durchlaufenen Zeilen, daher ist diese Metrik einfach umzusetzen. In der Praxis spielt sie aber nahezu keine Rolle.

Anweisungsüberdeckung (C_0-Test, statement coverage, Knotenüberdeckung)

Der Anweisungsüberdeckungsgrad hängt nicht von der Formatierung des Codes ab: hier wird das Verhältnis der durch den Test ausgeführten Anweisungen (Operationen) zur Anzahl aller Anweisungen berechnet.

Wir betrachten den gleichen Testfall (Parameter: {100}, erwartetes Ergebnis: 0):

assertEquals(0, rabattAusgeben({100}))          //Testfallbezeichner, Pseudojava

Vollständig ist die Anweisungsüberdeckung, sobald jede Anweisung von mindestens einem Testfall ausgeführt wurde; welche Eingaben man dafür braucht, liest man am Kontrollflussgraphen ab.

Manuelle Berechnung der Metrik

Im Kontrollflussgraphen markieren wir alle Anweisungsknoten, die durchlaufen wurden (Start-1-2-3-4-6-7-3-8-10-Stop):

Kontrollflussgraph der Rabatt-Methode, die beiden Anweisungen in den if-Blöcken sind nicht hervorgehoben

Wir können auch hier wieder bestimmen, wie viele Anweisungen von dem Test ausgeführt werden.

Überdeckungsgrad:

C_0 = \frac{\sum{Anzahl \: getesteter \: Anweisungen}}{\sum{Anzahl \:aller \: Anweisungen}} = \frac{8}{10} = 80\%

Bei unserer Ermittlung über den Kontrollflussgraphen kommen wir auf 80%.

Berechnung der Metrik durch Code Coverage Tools

CodeCoverage-Tools kommen gegebenenfalls auf andere Werte als unsere händische Berechnung. JaCoCo (Java Code Coverage) gibt für unsere Beispielmethode einen Wert von 75% aus:

Ausgabe von JaCoCo

Das Tool gibt auch seine Werte preis: 11 Anweisungen wurden nicht erreicht, 34 hat der Test erreicht. Es ergibt sich also:

C_0 = \frac{\sum{Anzahl \: getesteter \: Anweisungen}}{\sum{Anzahl \:aller \: Anweisungen}} = \frac{34}{34+11} = 75,6\%

Aber wie kommt JaCoCo darauf? Selbst in der Detailansicht sind keine 45 Anweisungen zu erkennen:

Detailausgabe von JaCoCo

Der Hintergrund ist, dass JaCoCo nicht die Anweisungen des Quelltextes zählt, sondern die Instruktionen des Java-Bytecodes: Zusammengesetzte Operationen bestehen aus mehreren Instruktionen, hinzu kommen interne Operationen wie beispielsweise bei der Iteration durch Arrays. Die Methode rabattAusgeben() besteht aus 45 Bytecode-Instruktionen.

Neben dem Aufteilen einzelner Operationen kann man den Kontrollfluss auch vereinfachen: alle unverzweigten Anweisungen (reine Sequenzen von Operationen) können in einem Knoten zusammengefasst werden. In unserem Beispiel betrifft das die Knoten 1+2 (Kanten a+b) sowie 6+7 (Kanten g+h):

Vereinfachter Kontrollfluss

Auch hier kommen wir auf andere Werte: wir haben nur noch 8 Knoten, von denen 6 durch den Testfall erreicht werden:

C_0 = \frac{\sum{Anzahl \: getesteter \: Anweisungen}}{\sum{Anzahl \:aller \: Anweisungen}} = \frac{6}{8} = 75\%

Warum mache ich das so kompliziert und stelle drei verschiedene Werte für ein und denselben Testfall vor? Was ist so wichtig daran?

Wir haben gesehen, dass die Ergebnisse aus CodeCoverage-Analysen unterschiedlicher Tools / unterschiedlicher Strategien nicht vergleichbar sind. Die Überdeckungsangaben in Prozent sind zudem weniger aussagekräftig als die Angabe als Bruch.

Über die Brüche bekommen wir wenigstens einen ersten Einblick in die genutzte Berechnung. Allen Ergebnissen ist gemeinsam: Sie helfen lediglich beim Vergleich im selben Umfeld. Eine Erhöhung der Testabdeckung erhöht die Berechnung unabhängig von zugrunde gelegter Berechnungsweise. Wir streben in jedem Fall eine Erhöhung der Abdeckungsmetrik an. Die einzig vergleichbaren Metriken - unabhängig von der Berechnung - wären 100% (alle Anweisungen erreicht) und 0% (keine einzige Anweisung erreicht). Diese Überlegungen gelten für die anderen Überdeckungsmetriken entsprechend.

Praktischer Nutzen: Testfälle ergänzen

Wie können wir die Metrik praktisch nutzen? Wenn wir nicht 100% der Anweisungen mit unseren Tests überdeckt haben, sollten wir die Testlücken analysieren. Am besten geht das natürlich mit einem Tool wie JaCoCo, das die Codezeilen farbig markiert.

  • Kann der Code überhaupt die Zeilen erreichen? Gegebenenfalls können tote Codeblöcke gelöscht werden.

  • Mit welchen Vorbedingungen und Parametern kommt der Test an dieser Stelle vorbei? Im besten Fall finden sich Testfälle, die auch die übrigen Zeilen erreichen.

In der Regel sollte danach ein Anweisungsüberdeckungsgrad von 100% erreicht werden können. Für die seltenen begründeten Ausnahmen sollte innerhalb des Teams ein Workaround zur Markierung usw. entwickelt werden.

Welche Testfälle wären in unserem Beispiel erforderlich?

Um zu den beiden fehlenden Anweisungen zu kommen, muss eine Position über 100 und die Summe abzüglich des Positionsrabatts über 1000 liegen. Bei einem Array mit nur einem Element muss dieses also über 1005 liegen; mit dem Element 1100 haben wir einen solchen Testfall erstellt:

@Test
public void rabattAusgeben_1100_Test() {
    // given: Preparation
    int[] kosten = { 1100 };

    // when: Execution
    int result = App.rabattAusgeben(kosten);

    // then: Verification
    int expected = 59;
    assertEquals(expected, result);
}

Wir identifizieren ihn fortan mit:

assertEquals(59, rabattAusgeben({1100}))          //Testfallbezeichner, Pseudojava

Wenn wir mit diesen Werten mal durch den Kontrollfluss navigieren, stellen wir obendrein fest: dieser eine Testfall alleine reicht komplett aus, um 100% Anweisungsüberdeckung zu erhalten!

Fazit zur Anweisungsüberdeckung

Der Grad der Anweisungsüberdeckung von Testfällen kann relativ leicht - auch toolgestützt - ermittelt werden. Häufig ist es relativ einfach, mit Hilfe von Tools oder Kontrollflussgraphen Testfälle zu ergänzen, um einen höheren Anweisungsüberdeckungsgrad zu erreichen. Die Aussagekraft eines hohen Anweisungsüberdeckungsgrads ist jedoch nicht sehr groß (siehe unten, Nachteile). Eine Anweisungsüberdeckung der Testfälle von 100% sollte in jedem Fall angestrebt werden, reicht jedoch alleine noch nicht als Qualitätskriterium für die Testgüte aus. Nur in begründeten Ausnahmefällen sollte gegen eine komplette Anweisungsüberdeckung der Tests verstoßen werden.

Vorteil:

  • leicht zu ermitteln

  • findet ggf. “toten Code” (nicht erreichbare Anweisungen)

Nachteil:

  • Code gilt bereits nach einem Durchlauf als durchlaufen, auch wenn bei weiteren Durchläufen geänderte Daten vorliegen

  • leere Zweige werden nicht gefunden (if ohne expliziten else -Zweig - wie im obigen Kontrollflussgraphen): Ob das Programm auch dann richtig arbeitet, wenn die optionale Anweisung nicht ausgeführt wird, prüft die Anweisungsüberdeckung nicht.

  • keine Unterscheidung nach Wichtigkeit von Codezeilen

  • Bei Code mit Exceptionhandling ist es oft schwierig, Testfälle für eine 100%ige Anweisungsüberdeckung zu erstellen.

Zweigüberdeckung (C_1-Test, branch coverage, Kantenüberdeckung oder edge coverage)

Der Zweigüberdeckungsgrad gibt an, wie viele Prozent der Zweige des Kontrollflussgraphen durch die Tests durchlaufen werden. Werden alle Zweige durchlaufen, wurden somit auch alle Anweisungen ausgeführt. Eine komplette Zweigüberdeckung beinhaltet somit automatisch eine komplette Anweisungsüberdeckung. Die Testdaten werden so gewählt, dass jeder Zweig jeder Verzweigung mindestens einmal durchlaufen wird – bei jeder Bedingung also sowohl der true- als auch der false-Ausgang, auch wenn dieser (wie bei einem if ohne else) keine eigene Anweisung enthält. Bei Schleifen zählen das Betreten des Schleifenrumpfs und das Verlassen der Schleife als die beiden Zweige. Umgekehrt gilt das nicht: Aus 100 % Anweisungsüberdeckung folgt keine 100 % Zweigüberdeckung.

Der Überdeckungsgrad wird definiert als:

C_1= \frac{überdeckte\ Zweige}{alle\ Zweige\ im\ Programm}

Um die möglichen Zweige aus dem Kontrollflussgraphen zu bestimmen, können unverzweigte Kontrollflüsse zusammengefasst werden (etwa der Ablauf 1-2-3 im Beispiel). Der resultierende Kontrollflussgraph hat drei binäre Verzweigungen, also sechs Zweige:

Zweige im Beispielcode

Manuelles Bestimmen der Zweigüberdeckung über die Kanten des Kontrollflussgraphen

Wir wollen für den letzten Testfall aus dem vorigen Abschnitt die Zweigabdeckung für unsere Rabatt-Methode berechnen (Eingabeparameter: {1100}, erwartetes Ergebnis: 59). Dieser Testfall hatte ja eine Anweisungsüberdeckung von 100% erreicht:

assertEquals(59, rabattAusgeben({1100}))          //Testfallbezeichner, Pseudojava

Wir navigieren mit dem genannten Eingabewert durch den Kontrollflussgraphen. Kanten, die wir durchschreiten, werden rot hinterlegt (Start-1-2-3-4-5-6-7-3-8-9-10-Stop):

Kantenüberdeckung mit dem Testfall assertEquals(59, rabattAusgeben({1100}))

Die Eingangs- und die Ausgangskante werden nicht berücksichtigt. Es ergibt sich eine Kantenüberdeckung von:

C_{1, Kanten}= \frac{überdeckte\ Kanten}{alle\ Kanten\ im\ Programm} = \frac{10}{12} = 83 \%

Aus Sicht des Kontrollflusses kommt es gar nicht auf jede einzelne Kante an, sondern darauf, wie viele der Verzweigungsvarianten (also: Zweige) durch den Test überdeckt wurden. Das Beispiel verfügt über drei binäre Verzweigungen, aus denen sich sechs Zweige ergeben, vier davon erreicht unser Testfall:

Kontrollflussgraph mit gekennzeichneten Verzweigungen

C_{1, Zweig}= \frac{überdeckte\ Zweige}{alle\ Zweige\ im\ Programm} = \frac{4}{6} = 67 \%

Wir stehen vor dem gleichen Problem wie bei der Anweisungsüberdeckung: wir können sie auf unterschiedlichen Wegen bestimmen. Uns interessieren aber nicht die konkreten Werte, sondern das Maximieren der Werte durch Ergänzen von Testfällen. Es ist völlig unerheblich, ob wir den Zweigüberdeckungsgrad C_{1, Zweig} optimieren oder den Kantenüberdeckungsgrad C_{1, Kante}: aus 100% Kantenüberdeckung folgt 100% Zweigüberdeckung und umgekehrt.

Automatische Berechnung der Zweigüberdeckung

Codecoverage-Tools weisen die Zweigüberdeckung in der Regel auch aus. JaCoCo zählt in unserem Beispiel 6 Zweige – je zwei für die for-Schleife und die beiden if-Verzweigungen, die leeren else-Zweige eingeschlossen (die Anzahl erhält man per Tooltip über dem Balken). Einen Zweig wertet JaCoCo nur dann als erreicht, wenn er tatsächlich durchlaufen wurde – auch einen leeren else-Zweig. Mit dem Testfall {1100} allein meldet JaCoCo daher wie unsere Rechnung 4 von 6 Zweigen.

Bei zusammengesetzten Bedingungen zählt JaCoCo allerdings die Sprünge im Bytecode, also jede Teilbedingung einzeln. Auch hier dürfen wir die Ergebnisse unterschiedlicher Berechnungen daher nicht ohne Weiteres vergleichen.

Ergänzung weiterer Testfälle, um 100% Zweigabdeckung zu erhalten

Wenn wir nur mit diesem einen Testfall testen, erreichen wir eine Zweigabdeckung von 67% (bei 100% Anweisungsüberdeckung).

assertEquals(59, rabattAusgeben({1100}))          //Testfallbezeichner, Pseudojava

Welche Zweige sind denn noch offen? Der Blick in den Kontrollflussgraphen verrät: Die beiden else-Zweige, also NOT(position>100) und NOT(summe-rabatt>1000) werden nicht erreicht. Es fehlen also Tests mit einer Position von höchstens 100 und einer rabattierten Summe von höchstens 1000. Beides hatte unser erster Testfall aus der Anweisungsüberdeckung bereits erfüllt. Eine vollständige Zweigüberdeckung erreichen wir somit mit den beiden Testfällen:

assertEquals(0, rabattAusgeben({100}))            //Testfallbezeichner, Pseudojava
assertEquals(59, rabattAusgeben({1100}))          //Testfallbezeichner, Pseudojava

Mit beiden Testfällen meldet auch JaCoCo 6 von 6 Zweigen:

Zweigüberdeckung mit den Testfällen {100} und {1100}: JaCoCo meldet 100 % der Instruktionen und 100 % der 6 Zweige

Von primitiven Zweigen

Verzweigungen sind nicht immer unabhängig voneinander. Durch überlappende, sich ausschließende oder identische Bedingungen kann die Anzahl wirklich unabhängiger (primitiver) Zweige geringer sein als die Anzahl der Verzweigungen. Ein etwas überzogenes Beispiel hierfür:

if (betrag>100) {System.out.println("Das ist viel Geld");}
if (betrag<=100) {System.out.println("Das ist wenig Geld");}
if ((betrag>100)&&(betrag>1000)) {
  System.out.println("Das ist superviel Geld");
}else{
  if (betrag<=100) {System.out.println("Das ist auch sehr wenig Geld");}
}

Der daraus folgende Kontrollflussgraph weist vier Verzweigungen auf und acht Zweige:

Kontrollflussgraph mit vielen linear abhängigen Verzweigungen

Viele der Bedingungen hängen aber voneinander ab. Dieselbe Logik lässt sich folgendermaßen darstellen:

if (betrag>100) {
  System.out.println("Das ist viel Geld");
  if (betrag>1000)  {System.out.println("Das ist superviel Geld"); }
}else {
  System.out.println("Das ist wenig Geld");
  System.out.println("Das ist auch sehr wenig Geld");
}

In dieser Variante liegen noch zwei Verzweigungen mit vier Zweigen vor.

Kontrollflussgraph mit linear unabhängigen Zweigen

In der Literatur werden oft nur diejenigen Zweige betrachtet, die nicht von anderen Zweigen abhängen (primitiv sind):

C_{1, primitiv} = \frac{überdeckte\ primitive\ Zweige}{alle\ primitiven\ Zweige\ im\ Programm}

In der Praxis ist eine Unterscheidung von primitiven Verzweigungen aber häufig nicht möglich. Bei einem angestrebten Zweigüberdeckungsgrad von 100% ist der Unterschied von primitiven und nicht primitiven Verzweigungen ohnehin unerheblich.

Fazit zur Zweigüberdeckung

Der Zweigüberdeckungsgrad stellt die in der Praxis relevanteste Metrik zur Überprüfung der Testgüte dar. Die Aussagekraft ist bereits deutlich höher als beim Anweisungsüberdeckungsgrad. Die Berechnung des Grads und die Bestimmung weiterer Testfälle zur Erhöhung des Überdeckungsgrads ist jedoch noch vergleichsweise einfach.

Da aus 100% Zweigüberdeckung auch eine komplette Anweisungsüberdeckung folgt, stellt eine komplette Zweigüberdeckung die Mindestanforderung an Tests dar.

Vorteil der Zweigüberdeckung:

  • Sie deckt im Gegensatz zum Anweisungsüberdeckungsgrad auch nicht erreichbare und leere Zweige auf.

Nachteile der Zweigüberdeckung

  • Es werden keine Abhängigkeiten zwischen Zweigen getestet (dazu würde Pfadüberdeckung (C_2) benötigt).

  • Weder Kombinationen von Bedingungen/Verzweigungen noch komplexere Zustände (Dateien, Betriebssystem) werden in die Zweigüberdeckungsmetrik einbezogen (dazu würde Bedingungsüberdeckung (C_3) benötigt).

  • Schleifen werden nicht systematisch getestet, da sie nur einmal durchlaufen werden müssen.

  • Auch hier - wie bei allen folgenden Kontrollflussmetriken - bei Programmcode mit vielen Ausnahmebehandlungen (Exceptions) ist es oft nur schwer möglich, Testfälle für eine komplette Abdeckung zu formulieren.

Bedingungsüberdeckung (C_3-Test, condition coverage, Termüberdeckung)

Bei der Zweigüberdeckung steht die Verzweigung des Kontrollflusses im Vordergrund. Für zusammengesetzte Bedingungen an einer Verzweigung ist daher nur relevant, ob diese insgesamt erfüllt ist oder nicht.

Anders beim Bedingungsüberdeckungsgrad: hier wird der Fokus auf jede einzelne atomare Teilbedingung gesetzt. Setzt sich eine Bedingung aus Teilbedingungen zusammen (z.B. (a>0) && (b>0)), so müssen Testfälle jede Teilbedingung mindestens einmal erfüllen (als true auswerten) und nicht erfüllen (als false auswerten). Mit “Bedingung” im Sinne der Formel ist also nicht die Anzahl der Bedingungen, sondern die Anzahl der Bedingungsauswertungen gemeint (jeweils true / false pro Bedingung).

C_3= \frac{überdeckte\ Bedingungsauswertung}{alle\ Bedingungsauswertungen\ im\ Programm}

Unser Ausgangsbeispiel verfügt über keine zusammengesetzte Bedingung (somit wäre C_3 = C_1). Wir erweitern die Rabattregeln daher:

Der Rabatt auf die Gesamtsumme soll nur gewährt werden, wenn der Kunde über eine Rabattkarte verfügt und der geforderte Mindestbetrag vorliegt. Es muss also ein Parameter rabattkarteVorhanden angefügt werden, der vor Gewährung des Rabatts überprüft wird:

public static int rabattAusgeben(int[] kosten, boolean rabattkarteVorhanden){
        int rabatt = 0;
        int summe = 0;
        for(int position:kosten){
            if (position > 100) {
                rabatt += 5;
            }
            summe += position;
        }
        if ((summe-rabatt > 1000) & (rabattkarteVorhanden == true)){
            rabatt +=(int)((summe-rabatt)*0.05);
        }
        return rabatt;
    }

Wir erhalten vier Bedingungen (Schleife, (position > 100), (summe-rabatt > 1000) und (rabattkarteVorhanden == true)), die jeweils einmal zu true und einmal zu false ausgewertet werden sollen. Somit ergeben sich die folgenden Bedingungsauswertungen:

  1. eine weitere kosten-Position ist vorhanden (Bedingung der for-Schleife erfüllt)

  2. keine weitere kosten-Position (Bedingung der for-Schleife nicht erfüllt, Verlassen der Schleife)

  3. (position > 100) erfüllt

  4. (position > 100) nicht erfüllt (else-Zweig)

  5. (summe-rabatt > 1000) erfüllt

  6. (summe-rabatt > 1000) nicht erfüllt (else-Zweig)

  7. (rabattkarteVorhanden == true) erfüllt

  8. (rabattkarteVorhanden == true) nicht erfüllt (else-Zweig)

Kontrollflussgraph mit den 8 gekennzeichneten Bedingungen

Wenn wir in unseren Testfällen den Parameter rabattkarteVorhanden ergänzen und mit dem Wert true belegen, erreichen wir mit folgenden beiden Testfällen wieder 100% Zweigüberdeckung:

assertEquals(59, rabattAusgeben({1100}, true))        //Testfallbezeichner, Pseudojava
assertEquals(0, rabattAusgeben({100}, true))          //Testfallbezeichner, Pseudojava

Wir erreichen dadurch jedoch noch nicht alle Einzelbedingungen:

C_3= \frac{überdeckte\ Bedingungen}{alle\ Bedingungen\ im\ Programm} = \frac{7}{8} = 87{,}5 \%

Um auch alle Bedingungen mit true und false belegt zu haben, müssen wir einen Durchlauf mit rabattkarteVorhanden = false anfügen. Im Beispiel können wir das am einfachsten in dem Durchlauf machen, in dem der Rabatt ohnehin nicht gewährt wird:

assertEquals(59, rabattAusgeben({1100}, true))         //Testfallbezeichner, Pseudojava
assertEquals(0, rabattAusgeben({100}, false))          //Testfallbezeichner, Pseudojava

Jetzt wäre auch der Bedingungsüberdeckungsgrad bei 100%.

Eine Erweiterung hiervon ist die Mehrfachbedingungsüberdeckung. In diesem Fall muss nicht nur jede Teilbedingung für sich getestet werden, sondern auch alle möglichen Kombinationen:

(summe-rabatt > 1000) (rabattkarteVorhanden == true)
false false
false true
true false
true true

Um für diesen Fall auf 100% Mehrfachbedingungsüberdeckung zu kommen, sind zwei weitere Testfälle nötig:

assertEquals(59, rabattAusgeben({1100}, true))         //Testfallbezeichner, Pseudojava
assertEquals(5, rabattAusgeben({1100}, false))         //Testfallbezeichner, Pseudojava
assertEquals(0, rabattAusgeben({100}, true))           //Testfallbezeichner, Pseudojava
assertEquals(0, rabattAusgeben({100}, false))          //Testfallbezeichner, Pseudojava

Teilweise wird die Idee der Mehrfachbedingungsüberdeckung durch logische short-circuit-Operatoren wie && ad absurdum geführt: Trifft die erste Teilbedingung nicht zu, wird die zweite gar nicht mehr ausgewertet, da sie auf das Ergebnis keinen Einfluss hat.

Fazit zur Bedingungsüberdeckung

Der Bedingungsüberdeckungstest erweitert und verfeinert den Zweigüberdeckungstest. Achtung: Eine 100%ige einfache Bedingungsüberdeckung schließt die Zweig- und Anweisungsüberdeckung nicht automatisch ein. Beispiel: Die Testfälle rabattAusgeben({1100}, false) und rabattAusgeben({100}, true) werten jede Teilbedingung einmal zu true und einmal zu false aus – die Gesamtbedingung ist aber in beiden Fällen false, der Rabatt auf die Gesamtsumme (Anweisung 9) wird nie berechnet. Erst die Mehrfachbedingungsüberdeckung (alle Kombinationen) oder eine Bedingungsüberdeckung, die zusätzlich jeden Ausgang der Gesamtbedingung verlangt (Bedingungs-/Entscheidungsüberdeckung, condition/decision coverage), schließt Zweig- und Anweisungsüberdeckung ein.

Bei komplexen Bedingungskonstrukten ist es ratsam, statt der Zweigüberdeckung die Testfälle auf Bedingungsbasis zu erstellen. Einige Tools weisen daher mit “branch coverage” direkt den Bedingungsüberdeckungsgrad aus.

Vorteil Bedingungsüberdeckung

  • kann bei komplexen Bedingungsverknüpfungen logische Fehler entdecken, die nach Zweigüberdeckung unentdeckt bleiben

Nachteil Bedingungsüberdeckung

  • Mehrfachbedingungsüberdeckung ist durch kombinatorische Komplexität oft nur schwer zu erreichen.

  • findet nur offensichtliche Einzelbedingungen. Werden die Bedingungen in Operationen versteckt, bleiben sie oft unentdeckt, wie in folgender Umschreibung des obigen Beispiels:

boolean rabattgueltig = (summe-rabatt > 1000) & (rabattkarteVorhanden == true)
if (rabattgueltig){
    rabatt +=(int)((summe-rabatt)*0.05);
}

Pfadüberdeckung (C_2-Test, path coverage)

Die bisherigen Metriken haben die unterschiedlichen Kombinationen der Verzweigungen nicht erfasst - wichtig war lediglich, dass jede Verzweigung einmal durchlaufen wurde. Bei der Pfadüberdeckung geht es darum, möglichst alle unterschiedlichen Wege zu testen, die der Kontrollfluss durch ein Programm nehmen kann. Ein Pfad ist ein Weg durch den Kontrollflussgraphen, der beim Aufruf der Methode beginnt und erst dort endet, wo die Methode verlassen wird – am Methodenende oder bei einem return. Jede return-Anweisung ist also ein eigener möglicher Endpunkt. Wer alle Pfade testet, durchläuft dabei zwangsläufig auch alle Zweige: Eine vollständige Pfadüberdeckung schließt die vollständige Zweigüberdeckung und damit auch die Anweisungsüberdeckung ein.

Zwei Pfade für unser ursprüngliches Programm hatten wir in den Testfällen mit einem Element als Übergabe schon beschrieben:

1. Pfad:
assertEquals(59, rabattAusgeben({1100}))          //Testfallbezeichner, Pseudojava
Kontrollflussgraph der Rabatt-Methode mit markiertem Pfad für das Array mit dem Element 1100: Rabatt je Position und Rabatt auf die Gesamtsumme

Mit obigem Testfall war der Anweisungsüberdeckungsgrad bereits bei 100%.

2. Pfad:
assertEquals(0, rabattAusgeben({100}))          //Testfallbezeichner, Pseudojava
Kontrollflussgraph der Rabatt-Methode mit markiertem Pfad für das Array mit dem Element 100: beide if-Blöcke werden übersprungen

Durch die Kombination der beiden Testfälle wurde der Zweigüberdeckungsgrad auf 100% angehoben.

3. Pfad:

Es gibt noch eine weitere Variante für ein Eingabearray mit nur einem Element:

assertEquals(5, rabattAusgeben({500}))          //Testfallbezeichner, Pseudojava
Kontrollflussgraph der Rabatt-Methode mit markiertem Pfad für das Array mit dem Element 500: nur der Rabatt je Position
4. Pfad:

Hinzu kommt eine Variante mit leerem Eingabearray:

assertEquals(0, rabattAusgeben({}))          //Testfallbezeichner, Pseudojava
Kontrollflussgraph der Rabatt-Methode mit markiertem Pfad für ein leeres Array: die Schleife wird nicht betreten
5. - X. Pfad:

Sobald Wiederholungen im Spiel sind, gibt es eine Vielzahl an Varianten mit unterschiedlichen Kombinationen an Eingabearrays. Es wird schnell ersichtlich, dass die Bestimmung aller möglichen Pfade bei nicht trivialen Programmstrukturen mit Wiederholungsstrukturen schnell unübersichtlich bis unmöglich wird.

Ein kleiner Auszug an weiteren Möglichkeiten für unser kurzes Beispielprogramm:

assertEquals(0, rabattAusgeben({50, 80, 90}))                                             //alle Werte<=100, Summe<1000
assertEquals(55, rabattAusgeben({100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100}))  //alle Werte<=100, Summe>1000
assertEquals(74, rabattAusgeben({101, 101, 1001}))                                         //alle Werte>100, Summe>1000
assertEquals(60, rabattAusgeben({506, 506}))                                               //alle Werte>100, Summe>1000
assertEquals(5, rabattAusgeben({50, 506}))                                                //erst <=100, dann >100; Summe<1000
assertEquals(5, rabattAusgeben({506, 50}))                                                //erst >100, dann <=100; Summe<1000
...

Ein Beispiel: Alle Positionen liegen bei höchstens 100, die Summe aber über 1000 – das geht nur mit mehreren Schleifendurchläufen (Testfall mit elf Positionen zu je 100):

Kontrollflussgraph der Rabatt-Methode mit markiertem Pfad: alle Positionen höchstens 100, Summe über 1000
Kontrollflussgraph der Rabatt-Methode mit mehreren farbig markierten Pfaden bei mehreren Schleifendurchläufen

Wenn man jedoch versucht, Pfadvarianten zusammenzufassen und ähnliche Varianten mindestens einmal zu testen (siehe Beispielbezeichnungen oben), dann kann die Pfadabdeckung einen großen Beitrag dazu leisten, die Güte der Tests zu erhöhen.

Unmögliche Pfade

Eine weitere Schwierigkeit bei der Bestimmung der möglichen Pfade durch einen Programmablauf ist, dass einige nicht möglich sind, weil die nötigen Bedingungen für diese Navigation sich gegenseitig ausschließen. Ein Beispiel für so einen Pfad wäre der Pfad aus unserem Beispielprogramm, bei dem kein Element übergeben würde, aber trotzdem (summe-rabatt>1000) erfüllt sein soll. Das ist logisch nicht möglich:

Kontrollflussgraph der Rabatt-Methode mit einem unmöglichen Pfad: Schleife nicht betreten, aber Rabatt auf die Gesamtsumme

Bestimmung der Metrik

Die Metrik “Pfadabdeckung” ist also in den wenigsten Fällen tatsächlich messbar - und wird daher von den Tools in der Regel nicht ausgewiesen. Sie ergibt sich - analog zu den anderen Überdeckungstestmetriken - als:

C_2= \frac{überdeckte\ Pfade}{alle\ Pfade\ durch\ das\ Programm}

Fazit zur Pfadüberdeckung

Der Pfadüberdeckungsgrad ist nur bei sehr einfachen Programmen ermittelbar, da nicht immer bekannt ist, wie viele Pfade tatsächlich möglich sind. Sollte die Pfadabdeckung in Abläufen mit Wiederholungsstrukturen gemessen werden, so muss eine Höchstzahl an Wiederholungen festgeschrieben werden.

Vorteil der Pfadüberdeckung:

  • als strukturierter Pfadtest in der Praxis hilfreich

  • kann bei der Bildung kontrollflussorientierter Äquivalenzklassen helfen.

Nachteil der Pfadabdeckung:

  • als Metrik selten bestimmbar, da Anzahl aller Pfade oft nicht bekannt

  • als vollständige Metrik kaum praktische Bedeutung, da sie ohne Einschränkungen (z. B. eine Höchstzahl an Schleifendurchläufen) in der Praxis oft nicht durchführbar ist

  • Bei Wiederholungsstrukturen kaum einsetzbar (nur mit Beschränkung der Schleifendurchlaufanzahl)

Pfade ohne Schleifen vollständig aufzählen

Enthält ein Algorithmus keine Schleifen, kannst Du alle Pfade vollständig angeben. Dazu kennzeichnest Du die Bedingungen (B1, B2, …) und die Anweisungen (A1, A2, …) und notierst je Pfad alle durchlaufenen Elemente in der Reihenfolge ihrer Abarbeitung. Als Beispiel dient eine Methode, die am Einlass einer Veranstaltung den Begrüßungstext erzeugt:

public static String einlass(int alter, boolean hatTicket, boolean istVip){
    String text = "";                           // A1
    if (hatTicket) {                            // B1
        text = "Willkommen";                    // A2
        if (istVip) {                           // B2
            text += " in der Lounge";           // A3
        }
    } else {
        text = "Bitte Ticket kaufen";           // A4
    }
    if (alter < 16) {                           // B3
        text += " (mit Begleitung)";            // A5
    }
    return text;                                // A6
}
Kontrollflussgraph der Methode einlass() mit den Bedingungen B1 bis B3 und den Anweisungen A1 bis A6 (mit KI erstellt)

Im Kontrollflussgraphen erkennst Du: Die Verzweigung B2 liegt nur im ja-Zweig von B1, B3 folgt danach unabhängig von B1 und B2. Liegen Verzweigungen hintereinander, multipliziert sich die Zahl der Möglichkeiten; liegen sie alternativ in verschiedenen Zweigen, wird addiert. Hier ergibt das (2 + 1) · 2 = 6 Pfade:

Alle Pfade der Methode einlass()
Nr. Pfad Beispiel-Testdaten (alter, hatTicket, istVip) Ergebnis
1 A1, B1, A2, B2, A3, B3, A5, A6 (15, true, true) "Willkommen in der Lounge (mit Begleitung)"
2 A1, B1, A2, B2, A3, B3, A6 (30, true, true) "Willkommen in der Lounge"
3 A1, B1, A2, B2, B3, A5, A6 (12, true, false) "Willkommen (mit Begleitung)"
4 A1, B1, A2, B2, B3, A6 (40, true, false) "Willkommen"
5 A1, B1, A4, B3, A5, A6 (14, false, false) "Bitte Ticket kaufen (mit Begleitung)"
6 A1, B1, A4, B3, A6 (25, false, false) "Bitte Ticket kaufen"

Achte beim Aufzählen auf drei typische Fehler: Leere Zweige (if ohne else) sind eigene Pfade, auch wenn sie keine Anweisung enthalten (Pfade 2, 4 und 6). Bedingungen gehören mit in den Pfad, denn sie werden ausgewertet. Und jede return-Anweisung ist ein eigener Austrittspunkt: Endet eine Methode vorzeitig, ist der Pfad dort zu Ende.

Wie viele Testfälle braucht man mindestens?

Ein Testfall durchläuft genau einen Pfad. Die Mindestanzahl an Testfällen für eine vollständige Überdeckung ermittelst Du, indem Du so wenige Pfade wie möglich auswählst, die zusammen alle geforderten Elemente enthalten:

  • Anweisungsüberdeckung: Für einlass() genügen 2 Testfälle, z. B. Pfad 1 (enthält A1, A2, A3, A5, A6) und Pfad 6 (enthält A4). A2 und A4 schließen sich aus, deshalb geht es nicht mit einem.

  • Zweigüberdeckung: Es werden 3 Testfälle benötigt. B1 muss einmal false sein – in diesem Testfall wird B2 nicht erreicht. B2 braucht aber zwei Testfälle mit hatTicket == true, einmal mit istVip == true und einmal mit false. Beispiel: Pfade 1, 4 und 6 (B3 ist dabei ebenfalls je einmal true und false).

  • Pfadüberdeckung: Jeder Pfad braucht einen eigenen Testfall, also 6.

Eine obere Schranke für die Zahl der Testfälle zur Zweigüberdeckung liefert die zyklomatische Komplexität nach McCabe: Anzahl der binären Verzweigungen + 1, hier 3 + 1 = 4. So viele linear unabhängige Pfade gibt es; mit ihnen sind immer alle Zweige überdeckt – oft kommt man aber mit weniger aus.

Schleifen und Rekursion: Hier genügt für die Anweisungsüberdeckung oft schon ein einziger Testfall, weil ein Durchlauf den Schleifenrumpf mehrfach mit unterschiedlichen Werten ausführen kann. In der Rabatt-Methode erreicht rabattAusgeben({1100}) bereits 100 % Anweisungsüberdeckung. Bei einer rekursiven Methode genügt ein Aufruf, bei dem sich die Methode mindestens einmal selbst aufruft: Dann werden der Rekursionsfall und der Abbruchfall durchlaufen. Für die Zweigüberdeckung brauchst Du so viele Testfälle, dass jede Bedingung auch einmal false wird – bei der Rabatt-Methode zwei, weil (summe-rabatt > 1000) pro Aufruf nur einmal ausgewertet wird.

Welche Überdeckung passt?

Welches Überdeckungsmaß Du anstrebst, hängt vom Code und vom Risiko ab:

Auswahl eines Überdeckungsmaßes
Situation im Code Geeignetes Maß und Begründung
Nur Sequenzen und Verzweigungen mit if und else Anweisungsüberdeckung ist das Minimum; besser Zweigüberdeckung, weil sie mit gleich wenig Aufwand mehr findet.
Optionale Anweisungen (if ohne else) Zweigüberdeckung: Nur sie verlangt auch den Fall, in dem die optionale Anweisung nicht ausgeführt wird. Die Anweisungsüberdeckung würde diesen leeren Zweig nie prüfen.
Zusammengesetzte Bedingungen (&&, \|\|) Bedingungs- oder Mehrfachbedingungsüberdeckung, weil die Zweigüberdeckung nur das Gesamtergebnis betrachtet. In sicherheitskritischer Software (z. B. Luftfahrt) wird eine verschärfte Bedingungsüberdeckung (MC/DC) gefordert.
Kurze, kritische Methode ohne Schleifen Pfadüberdeckung ist machbar, weil die Zahl der Pfade überschaubar bleibt, und deckt auch Abhängigkeiten zwischen Verzweigungen auf.
Schleifen oder Rekursion Pfadüberdeckung ist nicht realistisch, weil jeder zusätzliche Schleifendurchlauf neue Pfade erzeugt. Angestrebt wird Zweigüberdeckung, ergänzt um Tests mit 0, 1 und mehreren Durchläufen.

Ein Beispiel für eine Begründung: „Für die Methode wird Zweigüberdeckung angestrebt, weil sie eine optionale Anweisung enthält, deren Auslassen sonst nicht getestet würde. Pfadüberdeckung ist wegen der Schleife über alle Einträge nicht vollständig erreichbar.“

Überdeckungsmaße im Überblick

Die Maße bauen aufeinander auf: Wer alle Pfade testet, hat alle Zweige getestet; wer alle Zweige testet, hat alle Anweisungen ausgeführt. Die Umkehrung gilt jeweils nicht.

Überdeckungsmaße und wie sie aufeinander aufbauen
Maß (englisch) übliches Kürzel überdeckt, wenn … schließt ein
Anweisungsüberdeckung (statement coverage) C0 jede Anweisung mindestens einmal ausgeführt wurde –
Zweigüberdeckung (branch coverage, decision coverage) C1 jeder Zweig jeder Verzweigung (auch ein leerer) mindestens einmal durchlaufen wurde C0
Pfadüberdeckung (path coverage) C2 jeder Pfad vom Eintritts- zu einem Austrittspunkt mindestens einmal durchlaufen wurde C1, C0
einfache Bedingungsüberdeckung (condition coverage) C3 jede atomare Teilbedingung einmal true und einmal false war keine der anderen
Mehrfachbedingungsüberdeckung (multiple condition coverage) C3 alle Kombinationen der Teilbedingungen getestet wurden C1, C0, einfache Bedingungsüberdeckung

Die Kürzel C0 bis C3 sind nicht genormt; manche Bücher nummerieren anders oder unterteilen weiter (z. B. C3a, C3b). Eindeutig sind die Namen. Weitere englische Fachbegriffe: test case (Testfall), test suite (Sammlung von Testfällen), control flow graph (Kontrollflussgraph), node (Knoten), edge (Kante), entry point / exit point (Eintritts- / Austrittspunkt), dead code (toter, nicht erreichbarer Code).

Fazit

Die Überdeckungsgrade der Tests stellen ein erstes Indiz zur Beurteilung der Test- und Codequalität dar: mehr nicht, weniger aber auch nicht.

Eine hohe Testabdeckung garantiert nicht, dass man viele Fehler findet. Umgekehrt wird aber ein Schuh draus: Bei einer niedrigen Testabdeckung ist die Chance sehr hoch, dass Fehler unentdeckt bleiben.

Der größte Vorteil beim Arbeiten mit Überdeckungstestmetriken ist aber: Wir analysieren den Code, versuchen zu verstehen, warum unsere Tests nicht alle Zeilen/Zweige erreichen und erhöhen somit direkt bei der Implementierung der Tests die Qualität unseres Codes.

Darüber hinaus sorgt gut durch Tests abgedeckter Code dafür, dass wir keine Angst mehr vor Änderungen und Refaktorisierung haben müssen: der Code bleibt easy to change.

Die wahre Kraft entwickeln Whiteboxtests mit Überdeckungsmetrik aber erst im Zusammenspiel von vielen Komponenten. Ein gutes Vorgehen, zeiteffizient eine hinreichende Testgüte auf Unittest-Ebene zu erreichen, könnte sein:

  • Beschreibung der Anforderungen an ein Softwaremodul oder eine Programmmethode mit Blackbox-Systematik

  • Implementierung der Methode, bis alle Testfälle bestanden werden (gerne auch zyklisch, per TDD: red, green, refactor)

  • Bestimmung der Metriken für das Modul

  • Ergänzung von Testfällen, bis die Abdeckung im gewünschten bzw. angemessenen Maß vorhanden ist

Leitfragen

  1. Was zeichnet einen Whitebox-Test aus, und wie unterscheidet er sich vom Blackbox-Test?

Beim Whitebox-Test (Glassbox- oder Strukturtest) leitest Du die Testfälle aus der inneren Struktur des Testobjekts ab, also aus Quelltext und Kontrollfluss, und wählst die Testdaten so, dass bestimmte Anweisungen, Zweige oder Pfade durchlaufen werden. Beim Blackbox-Test entstehen die Testfälle allein aus der Spezifikation, ohne Blick in den Code; nur er bemerkt eine ganz fehlende Anforderung, deshalb ergänzen sich beide – siehe Abschnitt „Was zeichnet einen Whitebox-Test aus?“.

  1. Was bedeutet Testabdeckung bezogen auf die Anweisungen eines Quelltextes, und wie berechnest Du sie?

Testabdeckung bezogen auf die Anweisungen gibt an, wie viel Prozent aller Anweisungen die Testfälle mindestens einmal ausführen: C_0 = getestete Anweisungen / alle Anweisungen, z. B. 8 von 10 Anweisungen = 80 %. Gib den Wert am besten auch als Bruch an, weil Tools wie JaCoCo anders zählen und Prozentwerte verschiedener Berechnungen nicht vergleichbar sind – siehe Abschnitt „Grundlegende Berechnung der Überdeckungsgrade“ und Abschnitt „Manuelle Berechnung der Metrik“.

  1. Nach welchen Kriterien wählst Du die Testdaten für Anweisungs-, Zweig- und Pfadüberdeckung, wie bauen die Maße aufeinander auf, und warum bemerkt die Anweisungsüberdeckung leere Zweige nicht?

Für Anweisungsüberdeckung wählst Du die Testdaten so, dass jede Anweisung mindestens einmal ausgeführt wird, für Zweigüberdeckung so, dass jeder Zweig jeder Verzweigung (auch ein leerer) durchlaufen wird, für Pfadüberdeckung so, dass jeder Pfad vom Eintritts- zu einem Austrittspunkt vorkommt. Pfadüberdeckung schließt Zweig- und damit Anweisungsüberdeckung ein, nicht umgekehrt; leere Zweige (if ohne else) enthalten keine Anweisung und werden daher von der Anweisungsüberdeckung nie verlangt – siehe Abschnitt „Überdeckungsmaße im Überblick“ und Abschnitt „Fazit zur Anweisungsüberdeckung“.

  1. Wie findest Du heraus, welche Anweisungen oder Zweige mit gegebenen Testdaten nicht durchlaufen werden, und wie ergänzt Du die Testdaten?

Du verfolgst die Testdaten im Kontrollflussgraphen und markierst die durchlaufenen Knoten und Kanten, oder Du lässt ein Coverage-Tool wie JaCoCo die erreichten Zeilen farbig markieren. Für jede nicht erreichte Stelle prüfst Du, ob sie überhaupt erreichbar ist (sonst toter Code) und welche Eingaben dorthin führen – beim Rabattbeispiel ergänzt {1100} die Anweisungen in den if-Blöcken, {100} die leeren else-Zweige – siehe Abschnitt „Praktischer Nutzen: Testfälle ergänzen.“ und Abschnitt „Ergänzung weiterer Testfälle, um 100% Zweigabdeckung zu erhalten“.

  1. Wie notierst Du alle Pfade eines Algorithmus, und wie viele gibt es?

Du kennzeichnest Bedingungen (B1, B2, …) und Anweisungen (A1, A2, …) und notierst je Pfad alle durchlaufenen Elemente in der Reihenfolge ihrer Abarbeitung, auch leere Zweige und Bedingungen. Hintereinander liegende Verzweigungen multiplizieren die Zahl der Möglichkeiten, alternative werden addiert, bei einlass() also (2 + 1) · 2 = 6 Pfade; mit Schleifen wird die Zahl schnell unüberschaubar – siehe Abschnitt „Pfade ohne Schleifen vollständig aufzählen“.

  1. Wie viele Testfälle brauchst Du mindestens für eine vollständige Anweisungs-, Zweig- und Pfadüberdeckung – auch bei Schleifen und Rekursion?

Du wählst so wenige Pfade wie möglich, die zusammen alle geforderten Elemente enthalten: Bei einlass() reichen 2 Testfälle für Anweisungs-, 3 für Zweig- und 6 für Pfadüberdeckung; eine obere Schranke für die Zweigüberdeckung liefert die zyklomatische Komplexität. Bei Schleifen und Rekursion genügt für die Anweisungsüberdeckung oft ein einziger Testfall (z. B. rabattAusgeben({1100}) oder ein Aufruf mit mindestens einem Selbstaufruf), für die Zweigüberdeckung brauchst Du so viele, dass jede Bedingung auch einmal false wird – siehe Abschnitt „Wie viele Testfälle braucht man mindestens?“.

  1. Welches Überdeckungsmaß wählst Du für einen gegebenen Algorithmus, und wie begründest Du die Wahl?

Bei reinen if/else-Verzweigungen ist Anweisungsüberdeckung das Minimum, besser Zweigüberdeckung; bei optionalen Anweisungen ist Zweigüberdeckung nötig, bei zusammengesetzten Bedingungen Bedingungs- oder Mehrfachbedingungsüberdeckung. Pfadüberdeckung passt für kurze, kritische Methoden ohne Schleifen, bei Schleifen oder Rekursion ist sie nicht realistisch; Deine Begründung nennt die konkrete Codestruktur und das, was sonst ungetestet bliebe – siehe Abschnitt „Welche Überdeckung passt?“.

  1. Was unterscheidet Bedingungs- und Mehrfachbedingungsüberdeckung von der Zweigüberdeckung, und warum garantiert selbst eine Testabdeckung von 100 % keine Fehlerfreiheit?

Die Zweigüberdeckung betrachtet nur das Gesamtergebnis einer Bedingung, die Bedingungsüberdeckung verlangt, dass jede atomare Teilbedingung einmal true und einmal false ist, die Mehrfachbedingungsüberdeckung testet alle Kombinationen der Teilbedingungen (die einfache Bedingungsüberdeckung schließt die Zweigüberdeckung nicht ein). Selbst 100 % Abdeckung garantiert keine Fehlerfreiheit, weil sie nur zeigt, dass Code ausgeführt wurde – nicht, ob die Ergebnisse stimmen oder ob eine Anforderung ganz fehlt – siehe Abschnitt „Bedingungsüberdeckung (C_3-Test, condition coverage, Termüberdeckung)“ und Abschnitt „Fazit“.

Übungsaufgaben


Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Überdeckungsmetriken zur Bestimmung der Testgüte (Whitebox-Testing)” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/codecoverage veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/qs/testen. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 28.10.2026.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: