Java7以降におけるMavenでのカバレッジレポート


photo: http://www.flickr.com/photos/zzpza/

Java6がEOLとなったこともあり、コンパイルバージョンもJava7以降を指定するようになったので、標準的に利用するMavenカバレッジプラグインを見直しています。

Javaカバレッジツールとして、今のところ有名なものとしては、以下のものがあります。


Coberturaは、Javaカバレッジツールとしては情報量も実績も多くあります。私も、これまではCoberturaを使っていたのですが、Java7を利用するようになってからは、いろいろとエラーが発生するようになってしまいました。
JaCoCoは、情報量は少ないのですが、別のカバレッジツールであるEmmaを置き換えるためのカバレッジライブラリとして、EclEmma(Emmaを利用したEclipseプラグイン)の開発チームが、開発を進めてきたものです。

Coberturaでも、設定変更で対応できる場合もあるのですが、Cobertura自体、2年以上開発がストップしてしまっている状況でもあるので、今後はJaCoCoを利用しようかな、と考えています。


今回、それぞれの設定方法について調べたので、その内容をまとめておきます。

Cobertura レポート

基本設定をしてみる

まず初めに、Mavenにおけるコンパイルバージョンを1.7にした状態で、CoberturaのMavenプラグインであるcobertura-maven-pluginを設定して実行してみます。

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>example</groupId>
  <artifactId>maven-coverage</artifactId>
  <version>0.0.1</version>
  <packaging>jar</packaging>

  <properties>
    <jdk.version>1.7</jdk.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <site.encoding>UTF-8</site.encoding>
  </properties>

  <dependencies>
  <!-- Test -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <configuration>
            <source>${jdk.version}</source>
            <target>${jdk.version}</target>
          </configuration>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.2</version>
          <configuration>
            <locales>ja</locales>
            <inputEncoding>${project.build.sourceEncoding}</inputEncoding>
            <outputEncoding>${site.encoding}</outputEncoding>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

  <reporting>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-report-plugin</artifactId>
        <reportSets>
          <reportSet>
            <reports>
              <report>report-only</report>
            </reports>
          </reportSet>
        </reportSets>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>cobertura-maven-plugin</artifactId>
        <configuration>
          <formats>
            <format>html</format>
            <format>xml</format>
          </formats>
        </configuration>
      </plugin>
    </plugins>
  </reporting>

</project>

以下のコマンドにより、Mavenレポートを出力します。

  mvn clean site

しかしながら、そのまま実行すると、以下のようなエラーが出力されてしまいます。
(Coberturaのレポートはカバレッジが0%で出力されしまうのですが、Surefireのレポートの方で、エラーが出力されています)

java.lang.VerifyError: Expecting a stackmap frame at branch target ...

Java7ビルドのエラーを回避する

先ほどのエラーを回避するためには、JUitを実行するmaven-surefire-pluginプラグインの設定を以下のように変更します。

  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
      <argLine>-XX:-UseSplitVerifier</argLine>
    </configuration>
  </plugin>

今度は、カバレッジがきちんと出力されました。

それでも回避できないエラー

ただ、上記の設定をした場合でも、以下のようなエラーが発生するケースがあります。

  • テストケース実行時に ArrayIndexOutOfBoundsException が発生する
Caused by: java.lang.ArrayIndexOutOfBoundsException: 54
    at org.springframework.asm.Type.a(Unknown Source)
    at org.springframework.asm.Type.getType(Unknown Source)

これは、クラスパスに複数バージョンのasm.jarが存在するためのようです。
私の場合は、SpringFrameworkが依存関係に存在する場合に発生しました。
cobertura-maven-pluginのasm.jarを除外しようと思ったのですが、pom.xmlのreporting設定では、依存関係のjarを除外できないため、回避することができなさそうです。
また、このエラーも発生したり、しなかったりします。

  • テストクラスのコンパイル時に 「XxxXxx(クラス名)にアクセスできません」というエラーが発生する

Coberturaがテストクラスのコンパイルを行うときに発生します。
これも、Coberturaが行うコンパイル時の相性によって発生するようです。

JaCoCo レポート

基本設定をしてみる

JaCoCoのMavenプラグインである、jacoco-maven-pluginの設定を行います。
build設定の部分と、reporting設定の部分とで、2箇所に設定を行う必要があります。

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>example</groupId>
  <artifactId>maven-coverage</artifactId>
  <version>0.0.1</version>
  <packaging>jar</packaging>

  <properties>
    <jdk.version>1.7</jdk.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <site.encoding>UTF-8</site.encoding>
    <jacoco.include.package>example.*</jacoco.include.package>
  </properties>

  <dependencies>
  <!-- Test -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <configuration>
            <source>${jdk.version}</source>
            <target>${jdk.version}</target>
          </configuration>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.2</version>
          <configuration>
            <locales>ja</locales>
            <inputEncoding>${project.build.sourceEncoding}</inputEncoding>
            <outputEncoding>${site.encoding}</outputEncoding>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          <argLine>${jacocoArgs}</argLine>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>prepare-agent</id>
            <phase>test-compile</phase>
            <goals>
              <goal>prepare-agent</goal>
            </goals>
            <configuration>
              <propertyName>jacocoArgs</propertyName>
              <includes>
                <include>${jacoco.include.package}</include>
              </includes>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

  <reporting>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-report-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </reporting>

</project>

JaCoCoに対して、カバレッジを出力したいパッケージのルートを指定するようにしてください。そうしないと、余計なjarを読み込もうとして、エラーが発生することがあります。
また、Coberturaの場合と違い、maven-surefire-report-pluginでは、「report-only」の設定を除外しています。JaCoCoでは、自動でテストが行われないため、レポートの出力と同時に、JUnitが実行されるようにしておく必要があります。

Mavenを実行すると、以下のようなHTMLレポートが出力されます。

日本語のディレクトリに注意

JaCoCoは、カバレッジデータを以下のファイルに出力します。

  <basedir>/target/jacoco.exec

このファイルは、の途中に日本語を含むディレクトリがあると、適切な場所に出力されません。
自分でも、ちょっとハマりました(汗)

今後に向けて

詳細は確認できていないのですが、CoberturaとJaCoCoでは、カバレッジの値が一部異なるようです。
この辺りは、差分が生じる理由を確認する必要がありそうです。

ただ、JaCoCoは、Jenkinsプラグインもあるようなので、通常利用する分には問題無さそうです。

また、試してはいませんが、JaCoCoは、ScalaやKotlin、Xtendなどのカバレッジにも対応するようなので、有用性は高そうですね。
ということで、今後は、JaCoCoを利用していこうと思います。