Java
テスト
jacoco

Jacoco 0.8.0でJavaコードカバレッジをより快適に

JaCoCo(以降はjacocoと表記)はJavaのC0(ステップ)およびC1(ブランチ)のカバレッジおよびレポート作成を行うJavaライブラリです。

jacocoはとても便利なライブラリですが、一部のJava構文では「明らかにテストコードでは通っているのに、カバレッジではミス扱いになる」というケースがある課題があります。しかし先日リリースされたjacoco 0.8.0の登場により、多くの課題が解決されました。

まとめ

  • Jacoco v0.8.0にバージョンアップすることにより、以下のようなJDK7以降に導入されたJava構文のC0/C1カバレッジが正常に動作するようになります。
    • try-catch-resource句
    • Stringのswitch文
    • synchronized句
  • Maven, Gradleでは、jacocoプラグインのバージョンを0.8.0に指定することにより利用可能です。
  • Jenkinsのjacocoプラグイン(v2.2.1時点)は0.7.5しか利用できないため0.8.0は利用できません。(カスタムビルドをすれば可能かも)

背景

一部Java構文、およびJDK7以降に導入されたJava構文のJavaバイトコードはJavaソースコードでは表現されない不到達ルートが存在します。このためJavaソースコードレベルでC0/C1の100%カバレッジを達成できるテストを記述しても、何故かC0/C1カバレッジを100%にできない課題がありました。

jacoco 0.8.0ではこれらの特殊な不到達Javaバイトコードを除外して、より多くのJavaソースコードのカバレッジを適切に測定することができます。

補足:
C0/C1カバレッジ100%の妥当性については議論の余地があると思います。しかし一部の開発業務ではこの指標を満たすことでお金をもらっている現実があるため、このあたりは折り合いをつける必要があります。

jacoco 0.8.0で対応する主なJava構文

ここではjacoco 0.8.0でカバレッジが正常に動作するようになるJava構文について紹介します。特に重要なのはtry-catch-resource句String switch文への対応でしょう。

1. try-catch-resource句

try-catch-resource句はReader等のcloseを確実かつ簡潔に行うために便利なJava構文です。jacoco 0.8.0より前のバージョンでは「catchをすり抜ける例外」を発生させない限りカバレッジが100%になりませんでしたが、jacoco 0.8.0ではちゃんと100%になります。

public static void printFileContents(String fileName) {
    try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
        System.out.println(br.toString());
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    // 補足:カバレッジを100%にするためには「すり抜ける」例外発生パターンも実行する必要がある
}

詳細についてはIssue#500を参照。

2. Stringのswitch文

詳細はIssue#496を参照。

例えば以下のソースコードのカバレッジを網羅するために、A,B,C,Dの入力パターンを検証してもC0/C1のカバレッジが100%になりません。jacoco 0.8.0ではこのカバレッジが100%になります。

public static boolean stringSwitch(String s) {
    switch(s) {
        case "A": break;
        case "B": break;
        case "C": break;
        default: break;
    }
}

ビルド・CI関連ツール対応状況

ここではGradle、Maven、Jenkinsが提供するjacocoプラグインの「jacocoバージョン0.8.0」対応状況を整理します。Jenkins以外は利用可能です。

Gradle

結論:任意のjacocoバージョンを利用可能

Gradleでは標準でjacocoプラグインが提供されていますが、デフォルトで使用されるjacocoバージョンはやや古めとなっています。幸いなことにjacocoプラグインのプロパティのtoolVersionでjacocoバージョンを指定することが可能なため、0.8.0を指定しましょう。

build.gradle(抜粋)
apply plugin: 'jacoco'
jacoco {
    toolVersion = "0.8.0"
}

Maven

結論:任意のjacocoバージョンを利用可能

Gradleと同様に対応しています。jacoco公式ドキュメントにもMaven用jacocoプラグインに関する情報があります。jacoco的にはMaven側が本家っぽいですね。
http://www.eclemma.org/jacoco/trunk/doc/maven.html

pom.xml(抜粋)
<plugin>
  <groupId>org.jacoco</groupId>
  <artifactId>jacoco-maven-plugin</artifactId>
  <version>0.8.0</version>
</plugin>

Jenkins

結論:最新版jacocoプラグイン(v2.2.1)でもjacoco 0.7.5しか利用できない

Jenkinsのjacocoプラグインはv2.0.0でjacocoバージョンがv0.7.5にアップデートされてたのが最後のようです。変更履歴を確認する限りでは、現時点で最新のv2.2.1までjacocoバージョンのアップデートは施されていません。特にGradleのjacocoプラグインのようにバージョン指定もできないため、ソースコードを改造するなどの対応が必要です。

なお、GitHubのmasterレポジトリ上はjacoco 0.8.0に更新されているため、GitHubのソースコードをもとにプラグインをビルドすればjacoco 0.8.0を利用できるようになりそうです。

https://github.com/jenkinsci/jacoco-plugin/blob/master/pom.xml

jacoco-plugin/pom.xml(抜粋)
<jacoco.version>0.8.0</jacoco.version>

(おまけ) jacoco 0.8.0で導入されたフィルタリング関連クラス群

jacoco 0.8.0になってから多くのJava構文のカバレッジ問題が解決された理由は、不到達Javaバイトコードのフィルタリング関連の実装が加わったためです。今年2017年に入りコミッターのGodin氏が特殊ケースの不到達Javaバイトコードをフィルタリングする仕組みを導入したことにより、ソースコードレベルのC0/C1カバレッジを正常に判定できるようになりました。

このフィルタリング関連クラスの導入により「Javaソースコードレベルでは到達しない」Javaバイトコードルートをケース毎にフィルタすることができるようになり、カバレッジとしてカウントが不要な箇所を除外することが可能となりました。おそらく今後もフィルタリング関連クラスを追加することにより、正常にカバレッジできないケースに対応することになると思います。

フィルタ関連のクラスファイルはGitHubの下記ディレクトリに配置されています。
org.jacoco.core.internal.analysis.filter