Code Coverage Reports mit Maven in Multi-Modul-Projekten

07.02.2018

JaCoCo ist ein sehr nützliches Tool für die Messung der Code Coverage, also der Abdeckung von Produktivcode durch automatisierte Tests, sowie zur Erstellung von Code Coverage Reports. An sich ist das Einbinden des JaCoCo-Maven-Plugins nicht schwierig. Hat man allerdings in einem Maven-Projekt mehrere Module, ist die Konfiguration nicht mehr ganz so trivial. Denn standardmäßig werden Coverage-Reports nur für die einzelnen Module generiert. Um einen aggregierten Report über alle Module hinweg zu erhalten, der die Gesamttestabdeckung des Projekts zeigt, und dabei außerdem zwischen Unit- und Integrations-Tests unterscheiden soll, ist schon etwas mehr Aufwand nötig. Leider gibt es kaum funktionierende Beispiele im Internet. Zudem ist die Dokumentation etwas dürftig, so dass man schon mal eine ganze Weile beschäftigt sein kann bis alles wie gewünscht funktioniert.

Daher haben wir ein funktionsfähiges Beispielprojekt mit drei Modulen erstellt, das als Vorlage für Projekte dienen kann.

Beispielprojekt auf Github

Das Beispielprojekt ist auf Github verfügbar, und zwar in zweierlei Ausprägungen:

Mit JUnit 5 (Java 8 vorausgesetzt):

https://github.com/doubleSlashde/maven-multimodule-coverage/tree/master

Mit JUnit 4:

https://github.com/doubleSlashde/maven-multimodule-coverage/tree/junit-4

Die Code Coverage Reports

Das Projekt erzeugt getrennte Code Coverage Reports für Unit– und Integrations-Tests im HTML-Format – jeweils aggregiert über alle Module hinweg – sowie einen kumulierten Overall-Report, der die Gesamt-Testabdeckung des Projekts unter Berücksichtigung sowohl der Unit-Tests als auch der Integrations-Tests zeigt:

Via Klick auf die Modulnamen kann man jeweils die Testabdeckung einzelner Module einsehen.

Konfiguration von JaCoCo

JaCoCo verwendet sogenannte Agents, um Daten über Aufrufe des getesteten Codes während der Testausführung zu sammeln. Damit die Agenten für Mavens Test-Plugins „surefire“ (Ausführung von Unit-Tests) und „failsafe“ (Ausführung von Integrations-Tests) aktiviert werden, bietet das JaCoCo-Maven-Plugin die beiden Goals prepare-agent sowie prepare-agent-integration an. Mit dessen Hilfe wird eine Maven-Property gesetzt, die den beiden Test-Plugins als VM-Parameter mitgegeben werden kann, um die JaCoCo-Agenten zu aktivieren. Der Name dieser Property lautet für die beiden Goals prepare-agent sowie prepare-agent-integration standardmäßig gleich, nämlich argLine. Möchte man in den Reports zwischen Unit-Tests und Integrations-Tests unterscheiden, muss man via Konfiguration des jacoco-maven-plugin‘s zwei unterschiedliche Property-Namen konfigurieren.

In folgendem Auszug aus der Parent-pom.xml geschieht dies in den Zeilen 12 und 22:

  <plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <executions>
      <!-- set arg line property for surefire -->
      <execution>
        <id>prepare-agent</id>
        <goals>
          <goal>prepare-agent</goal>
        </goals>
        <configuration>
          <propertyName>surefireArgLine</propertyName>
        </configuration>
      </execution>
      <!-- set arg line property for failsafe -->
      <execution>
        <id>prepare-agent-integration</id>
        <goals>
          <goal>prepare-agent-integration</goal>
        </goals>
        <configuration>
          <propertyName>failsafeArgLine</propertyName>
        </configuration>
      </execution>
    </executions>
  </plugin>

Jetzt müssen surefire und failsafe noch so konfiguriert werden, dass ihnen die zuvor konfigurierte Property als VM-Parameter mitgegeben wird. Beides wird ebenfalls in der Parent-pom.xml konfiguriert.

Für das surefire-Plugin sieht dies folgendermaßen aus (setzen des VM-Parameters für den Agent in Zeile 7):

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>2.19.1</version>
  <configuration>
    <!-- Sets the VM argument line used when unit tests are run. -->
    <argLine>${surefireArgLine}</argLine>
  </configuration>
</plugin>

Dasselbe gilt für surefire. Hier ist die Konfiguration etwas umfangreicher. Da die Ausführung der Integrationstests durch failsafe in der Phase integration-test passiert, wird die Konfiguration via executions-Spezifikation an die Phase integration-test sowie das goal verify gebunden wird. Das Setzen des VM-Parameters für den Agent wird in Zeile 14 konfiguriert:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-failsafe-plugin</artifactId>
  <version>2.19.1</version>
  <executions>
    <execution>
      <id>integration-test</id>
      <goals>
        <goal>integration-test</goal>
        <goal>verify</goal>
      </goals>
      <configuration>
        <!-- Sets the VM argument line used when integration tests are run. -->
        <argLine>${failsafeArgLine}</argLine>
      </configuration>
    </execution>
  </executions>
</plugin>

Führt man nun mvn verify aus, sammelt der JaCoCo-Agent die Ausführungsdaten und legt diese in den target-Ordnern der einzelnen Anwendungsmodule ab. Für Unit-Tests landen diese in der Datei jacoco.exec, für die Integrationstest in jacoco-it.exec.

Erstellen der aggregierten Code Coverage Reports

Um einen über alle Anwendungsmodule aggregierten Code Coverage Report zu erhalten, wird ein weiteres Untermodul benötigt, das von allen zu berücksichtigten Modulen abhängt. Der pom.xml dieses dedizierten coverage-Moduls wird in der jacoco-maven-plugin-Konfiguration für jeden Reporttyp ein executions-Abschnitt mit goal report-aggregate hinzugefügt. Hier gibt man auch die Phase des Maven-Lebenszyklus an, in der die Reportaggregierung stattfinden soll. Für Unit-Tests eignet sich hier die Phase test, für die Integrationstests sowie den kumulierten Overall-Report die Phase verify.

Unter execution/configuration/title kann zudem der Reporttitel konfiguriert werden.

Nachfolgend die Maven-Konfiguration am Beispiel des aggregierten Unit-Test-Reports:

<plugin>
  <groupId>org.jacoco</groupId>
  <artifactId>jacoco-maven-plugin</artifactId>
  <executions>
    <!-- aggregated unit test coverage report -->
    <execution>
      <id>aggregate-reports-ut</id>
      <phase>test</phase>
      <goals>
        <goal>report-aggregate</goal>
      </goals>
      <configuration>
        <title>Maven Multimodule Coverage Demo: Coverage of Unit Tests</title>
        <outputDirectory>${project.reporting.outputDirectory}/jacoco-aggregate-ut</outputDirectory>
        <dataFileExcludes>
          <!-- exclude coverage data of integration tests --> 
          <dataFileExclude>**/target/jacoco-it.exec</dataFileExclude>
        </dataFileExcludes>
      </configuration>
    </execution>
    <!-- ... (configurations of integration tests and overall coverage report) -->
</plugin>

Nun werden die aggregierten Code Coverage Reports für Unit-Tests, Integrationstests sowie der kumulierte Overall-Report beim Ausführen von mvn verify erzeugt und im target-Verzeichnis des coverage-Moduls unter site (/coverage/target/site/) abgelegt:

jacoco-aggregate-ut Code Coverage Report der Unit-Tests
jacoco-aggregate-it Code Coverage Report der Integration-Tests
jacoco-aggregate-all Kumulierter Overall Code Coverage Report

Möchte man lediglich den Code Coverage Report der Unit-Tests haben, ist das Ausführen von mvn test ausreichend.

Wichtig!

Wird dem Projekt ein neues Modul hinzugefügt, muss dieses in der pom.xml des coverage-Moduls als Dependency hinzugefügt werden.
Ansonsten wird die Testabdeckung des neuen Moduls in den aggregierten Reports nicht berücksichtigt!

Zurück zur Übersicht

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*Pflichtfelder

*