Jun 232011
 

Testmodule

Maven-Module verwalten Produktiv- und Testcode in getrennten Verzeichnissen (src/main und test). Das Packaging (z.B. als JAR) exkludiert Testklassen und -ressourcen. Damit andere Module Test-Hilfsklassen, Mock-Implementierungen, etc. verwenden können, müssen diese in src/main liegen. Elemente von src/test bleiben für andere Module verborgen. Um Testklassen wiederzuverwenden, lagert man in diese in ein separates Testmodul aus. Das sieht z.B. wie folgt aus:

image

Der Testcode und die Hilfsklassen für “framework” sind nach “framework-test” ausgelagert worden. Die Test-Hilfsklassen operieren auf “framework”-Klassen. Die Testklassen benötigen die Testhilfsklassen. Würden die Tests innerhalb von “framework” liegen gebe es einen Modulzyklus. Dies ist weder sinnvoll noch von Maven erlaubt.

image 

Wer seine Testabdeckung automatisiert ermittelt bekommt mit dieser Modulaufteilung Probleme. Per Default ermitteln Cobertura, Clover, Emma die Unit-Test-Abdeckung nur pro Modul. Obwohl umfangreiche Tests für den Code in “framework” existieren, liefert eine Messung 0% Abdeckung.

Lösung mit JaCoCo

JaCoCo wird von den Emma-Machern entwickelt und arbeitet über Byte-Code-Instrumentation zur Laufzeit mittels eines JVM-Agents.

Mittels JaCoCo-Agent und dem JaCoCo-Sonar-Plugin lässt sich das Testabdeckungsproblem lösen und sogar die Abdeckung für Integrationstests messen. Das im Folgenden beschriebene Setup umfasst Jenkins / Hudson sowie Sonar Konfiguration und Anpassungen an Euren Maven POMs.

1. JaCoCo bereitstellen

Das “jacoco-agent.jar” wird auf dem System, welches die Tests ausführt (z.B. Euer CI Server), benötigt.

  1. ZIP herunterladen (wget …) z.B. nach /var/lib/jacoco. Achte darauf, dass Deine Sonar-Installation zum JaCoCo-Agent kompatibel ist oder entschließe Dich, Deine Sonar-Installation upzugraden.
  2. unzip
2. POM anpassen

Das Surefire Plugin muss den JaCoCo-Agent einbinden, wenn es die Tests ausführt. Der Agent muss Coverage-Informationen für alle Eure Module in dieselbe Datei schreiben.

   1: <profiles>

   2:   <profile>

   3:     <id>ci</id>

   4:       <build>

   5:         <pluginManagement>

   6:           <plugins>

   7:             <plugin>

   8:               <artifactId>maven-surefire-plugin</artifactId>

   9:                 <configuration>

  10:                   <argLine>-javaagent:${jacoco.agent.path}=destfile=${jacoco.unit.path}</argLine>

  11:                 </configuration>

  12:               </plugin>

  13:             </plugins>

  14:           </pluginManagement>

  15:       </build>

  16:   </profile>

  17: </profiles>

3. Jenkins / Hudson Job konfigurieren

Jenkins muss die Variablen “jacoco.agent.path” und “jacoco.unit.path”. Dazu bei “Goals und Optionen” in der Job-Konfiguration z.Bsp. folgendes angeben:

clean install -P ci -Djacoco.agent.path="/var/lib/jacoco/lib/jacocoagent.jar" -Djacoco.unit.path="/tmp/mapgame-jacoco-unit"

Gib bei der Sonarkonfiguration des CI-Jobs die Option “-Dsonar.jacoco.itReportPath=/tmp/mapgame-jacoco-it“ an. Das JaCoCo-Sonar-Plugin erkennt diese System-Property und findet dadurch die Report-Datei.

image

4. Sonar JaCoCo Plugin installieren

Installiere im Sonar Update Center das Plugin “JaCoCo”. Konfiguriere unter Sonar – Settings – Core – Code Coverage Plugin den Wert “jacoco”. Hast Du alles korrekt gemacht, so wirst Du auf Deinem Sonar-Dashboard nach Lauf des CI-Jobs im Jenkins / Hudson den korrekten Wert für die Unit-Testabdeckung sehen.

image image

 

Trennung Unit- und Integration-Tests

JaCoCo kann natürlich auf die Integration-Test-Abdeckung messen. Dies solltest Du getrennt von der Unit-Test-Messung halten. Mit Unit-Tests kann nach TDD entwickelt werden, mittels Integration-Tests nicht wirklich, welche Change & Test-Phase zu lang dauert und Unit-Tests eine viel lokalere Betrachtung erlauben. Dadurch ist Bugfixing bei Testfehlschlägen effizienter.

Für Integrationstests sollte das “maven-failsafe-plugin” verwendet werden, da es Aufräumarbeiten nach Integrationstests (Server stoppen, etc.) besser beherrscht (post-integration-test Phase wird bei Test-Fehlschlägen aufgerufen. Dies ist bei Surefire nicht der Fall.).

1. POM erweitern

Die Code-Abdeckung in den Integration-Tests selbst soll gemessen werden:

<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<argLine>-javaagent:${jacoco.agent.path}=destfile=${jacoco.it.path}</argLine>
</configuration>
</plugin>

Natürlich musst Du den JaCoCo-Agent auch in den getesteten Systemen einbinden. Bei mir ist dies die lokale Google App Engine.

<profiles>
<profile>
<id>ci</id>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>net.kindleit</groupId>
<artifactId>maven-gae-plugin</artifactId>
<executions>
<execution>
<id>start-app-engine</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
<configuration>
<javaAgent>${jacoco.agent.path}=destfile=${jacoco.it.path}</javaAgent>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>
</profiles>

2. Jenkins / Hudson CI Job erweitern
  • Zusätzlicher Parameter bei “Goals und Optionen”: -Djacoco.it.path="/tmp/mapgame-jacoco-it"
  • Zusätzlicher Parameter bei Sonar MAVEN_OPTS: -Dsonar.jacoco.itReportPath=/tmp/mapgame-jacoco-it
3. Sonar Dashboard erweitern
  1. Im Sonar Dashboard “configure widgets” klicken
  2. “IT coverage widget” hinzufügen

 

Fazit

JaCoCo funktioniert!

Hohe Code-Abdeckung durch gut strukturierte Unit- und Integrationstests, die jeweils nur eine “Sache” testen, sind die halbe Miete für gute Softwarequalität!

  2 Responses to “Multi-Modul Testabdeckungsanalyse mit JaCoCo”

  1. Hi,

    Coole Sache. Habs auch mal ausprobiert aber leider kommt bei mir ein Fehler bei dem Befehl von Punkt 3. Kann es daran liegen, dass ich Windows nutze und dadurch der Pfad anders angegeben werden muss? Irgendwie scheint er mit dem Punkt nich klar zu kommen.

    Bei dem Befehl:
    mvn clean install -Djacoco.agent.path=”tmp\jacocoagent.jar” -Djacoco.file.path=”tmp\mapgame-jacoco-unit”

    erhalte ich folgende Fehlermeldung:

    [ERROR] Unknown lifecycle phase “.agent.path=tmp/jacocoagent.jar”. You must specify a valid lifecycle phase or a goal in the format : or :[:]:. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile, process-classes, generate-test-sources, process-test-sources, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy, pre-clean, clean, post-clean, pre-site, site, post-site, site-deploy. -> [Help 1]
    [ERROR]
    [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
    [ERROR] Re-run Maven using the -X switch to enable full debug logging.
    [ERROR]
    [ERROR] For more information about the errors and possible solutions, please read the following articles:
    [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/LifecyclePhaseNotFoundException

    tmp liegt bei mir auf der gleichen Ebene wie pom.xml, also der Ausführungsort.

    Ich hoffe, du kannst mir weiterhelfen. Vielen dank

    • Offenbar übergibt Jenkins / Hudson die “Goals and Options” an maven anders. Probier es mal mit:
      mvn -Djacoco.agent.path=”C:\jacocoagent.jar” -Djacoco.file.path=”C:\mapgame-jacoco-unit” clean install
      Die Optionen sind bei “mvn” vor den Goals anzugeben.

Leave a Reply to Nico Cancel reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>