Kontakt
stefan.bente[at]th-koeln.de
+49 2261 8196 6367
Discord Server
Prof. Bente Personal Zoom
Adresse
Steinmüllerallee 4
51643 Gummersbach
Gebäude LC4
Raum 1708 (Wegbeschreibung)
Sprechstunde nach Vereinbarung
Terminanfrage: calendly.com Wenn Sie dieses Tool nicht nutzen wollen, schicken Sie eine Mail und ich weise Ihnen einen Termin zu.

JaCoCo (Java Code Coverage) ist ein weit verbreitetes Werkzeug zur Messung und Optimierung der Testabdeckung in Java-Projekten. Es zeigt genau, welche Codezeilen, Branches und Methoden durch Tests abgedeckt sind, und hilft dabei, ungetestete Bereiche systematisch zu identifizieren und die Codequalität langfristig zu verbessern.

Beispielcode-Repository
https://gitlab.com/archi-lab/public/testing-good-practices/-/tree/main/jacoco-example
Keywords
Testabdeckung, Code Coverage, JaCoCo, Java Code Coverage, Test

Testabdeckung messen mit JaCoCo

JaCoCo (Java Code Coverage) misst, welche Teile Ihres Produktivcodes beim Ausführen der Tests tatsächlich durchlaufen wurden. Das Werkzeug protokolliert während des Testlaufs jede ausgeführte Anweisung und jeden genommenen Verzweigungszweig und erzeugt daraus einen HTML-Report. So sehen Sie auf einen Blick, welche Zeilen, Zweige und Methoden von Ihren Tests gar nicht berührt werden, also blinde Flecken Ihrer Testsuite.

Wichtig ist die Blickrichtung: Coverage sagt Ihnen, was nicht getestet ist. Ungetesteter Code ist ein sicheres Warnsignal. Hohe Coverage ist umgekehrt keine Garantie für gute Tests (dazu unten mehr).

Wie Sie JaCoCo lokal ausführen und den Report finden, beschreibt die Infopage JaCoCo und PIT lokal ausführen.

Den Report lesen

1. Übersichtstabelle

Die Einstiegsseite listet alle Packages auf, darunter (per Klick) die Klassen und Methoden. Pro Zeile sehen Sie farbige Balken und Prozentwerte. Die beiden wichtigsten Spalten sind:

  • Missed Instructions / Cov. - Anteil der ausgeführten Bytecode-Anweisungen. Der grobe Gesamtüberblick.
  • Missed Branches / Cov. - Anteil der abgedeckten Verzweigungen. Das ist die ehrlichere Zahl: Eine if-Bedingung gilt erst dann als voll abgedeckt, wenn Ihre Tests sie sowohl wahr als auch falsch durchlaufen haben.

Die Farbbalken sind rot (nicht abgedeckt), gelb (teilweise) und grün (abgedeckt). Klicken Sie sich von einem roten oder gelben Eintrag bis auf Methodenebene durch.

JaCoCo-Übersichtstabelle

2. Quelltextansicht

Ein Klick auf eine Klasse zeigt den Quelltext, Zeile für Zeile eingefärbt:

  • Grün - die Zeile wurde vollständig ausgeführt.
  • Gelb - die Zeile enthält eine Verzweigung, von der nur ein Teil der Zweige durchlaufen wurde (z. B. das if war immer nur true, nie false).
  • Rot - die Zeile wurde überhaupt nicht ausgeführt.

Am linken Rand stehen zusätzlich kleine Rauten für die Verzweigungsabdeckung: grün = alle Zweige getroffen, gelb = nur ein Teil, rot = keiner. Fahren Sie mit der Maus darüber, dann zeigt JaCoCo z. B. “1 of 2 branches missed”.

JaCoCo-Quelltextansicht

Das Beispiel: RefundPolicy

RefundPolicy.refundPercent(...) entscheidet, wie viel Prozent eines Ticketpreises bei einer Stornierung erstattet werden. Die Methode hat vier Ausgänge, gestaffelt nach Vorlauf und Tarif:

    public int refundPercent( long hoursBeforeDeparture, boolean flexibleTicket ) {
        if ( hoursBeforeDeparture <= 0 ) {
            return 0;                       // Zug bereits abgefahren
        }
        if ( flexibleTicket || hoursBeforeDeparture >= ONE_WEEK_HOURS ) {
            return 100;                     // Flextarif, oder mindestens eine Woche vorher
        }
        if ( hoursBeforeDeparture >= ONE_DAY_HOURS ) {
            return 50;                      // mindestens einen Tag vorher
        }
        return 25;                          // weniger als ein Tag, aber noch nicht abgefahren
    }

Die mitgelieferte Testsuite ist bewusst lückenhaft. Ihre Tests sind nicht falsch, sie prüfen sogar exakte Werte, decken aber nur zwei der vier Ausgänge ab: die beiden 100-Prozent-Fälle und den 50-Prozent-Fall.

class RefundPolicyTest {
    private final RefundPolicy refundPolicy = new RefundPolicy();

    @Test
    void flexibleTicketsAreAlwaysFullyRefunded() {
        assertEquals( 100, refundPolicy.refundPercent( 5, true ) );
    }

    @Test
    void cancellingAtLeastAWeekAheadIsFullyRefunded() {
        assertEquals( 100, refundPolicy.refundPercent( 8 * 24, false ) );
    }

    @Test
    void cancellingAFewDaysAheadIsHalfRefunded() {
        assertEquals( 50, refundPolicy.refundPercent( 2 * 24, false ) );
    }
}

Genau hier spielt JaCoCo seine Stärke aus. Im Report sehen Sie sofort, was fehlt:

  • Die Zeile return 0 (Zug bereits abgefahren) ist rot. Kein Test ruft die Methode mit hoursBeforeDeparture <= 0 auf.
  • Die Zeile return 25 (Last-Minute-Stornierung) ist rot. Dieser Fall wird nie erreicht.
  • Zwei der if-Bedingungen (der Wächter <= 0 und die Tagesgrenze >= 24) zeigen eine gelbe Raute: Sie wurden nur in eine Richtung durchlaufen, der jeweils andere Zweig fehlt (“1 of 2 branches missed”). Die zusammengesetzte Bedingung darüber ist dagegen voll abgedeckt.

Der Report zeigt also Zeile für Zeile, welche Fälle die Tests vergessen haben. Sie ergänzen pro roter Zeile und pro gelber Raute einen Test, lassen JaCoCo erneut laufen, und die Stelle wird grün.

Was Coverage nicht leistet

Coverage zählt nur, ob eine Zeile ausgeführt wurde, nicht, ob Ihr Test danach etwas Sinnvolles prüft. Ein Test ganz ohne Zusicherung kann eine Zeile grün färben und würde trotzdem keinen Fehler bemerken. Volle Abdeckung ist also notwendig, aber nicht hinreichend. Wie wirksam Ihre Tests den abgedeckten Code wirklich prüfen, misst erst das Mutationstesten. Siehe dazu die Infopage zu PIT.