JaCoCoでカバレッジはどこまで確認できるか？

## この記事の目的
JaCoCoでカバレッジがどこまで確認できるか？について機会があったので調べました。  
すぐ忘れてしまいそうなので個人的なメモとして記します。

## 結論
JaCoCoは条件網羅を見るには十分実用的であるが、複合条件網羅までは見れない。

## JaCoCoのCoverage定義
https://www.jacoco.org/jacoco/trunk/doc/counters.html
を見ると、明確にサポートしているのは、
C0:命令網羅 (statement coverage, instructions coverage)
C1:分岐網羅 (branch coverage) 
であると読み取れます。

## そもそもC0～C2の定義は？
経験則としてC2については各プロジェクトや現場で微妙に解釈が揺れているケースが多いと思います。

ここでは、
[ホワイトボックステストにおけるカバレッジ（C0/C1/C2/MCC）について](https://qiita.com/odekekepeanuts/items/8b6542467d2a0066e5af) 
に沿って定義しておきます。

* C0 命令網羅 (statement coverage)
* C1 分岐網羅 (branch coverage)
* C2 条件網羅 (condition coverage)
* MCC 複合条件網羅 (multiple condition coverage)

先ほど書いたようにC2とMCCが混同されているケースが多いように思います。

## サンプルコード

### Target Code (Testee)
```java
package com.example.util;

public class Coverage {

    public static void or(int x , int y) {

        if (x == 0 || y <= 10) {
            System.out.println("true");
        } else {
            System.out.println("false");
        }
    }

    public static void and(int x , int y) {

        if (x == 0 && y <= 10) {
            System.out.println("true");
        } else {
            System.out.println("false");
        }
    }

    public static void caseStatement(int x) {
        switch (x) {
            case 0:
                System.out.println("0");
                break;
            case 1:
            case 2:
            case 3:
            case 4:
                System.out.println("1~4");
            default:
                System.out.println("default");
        }
    }

    public static void ifStatement(int x) {
        if (x == 0) {
            System.out.println("0");
        } else if (x == 1 || x == 2 || x == 3 || x == 4) {
            System.out.println("1~4");
        } else {
            System.out.println("default");
        }
    }
}
```

### Test Code (tester)
```java
package com.example.util;

import org.junit.jupiter.api.Test;

public class CoverageTest {

    @Test
    public void testCoverage() {
        var coverage = new Coverage();
    }
    @Test
    public void testOrTrueTrue() {
        // True || True  -> True
        Coverage.or(0, 10);
    }

    @Test
    public void TestOrTrueFalse() {
        // True || False -> True
        Coverage.or(0, 11);
    }

    @Test
    public void TestOrFalseTrue() {
        // False || True -> True
        Coverage.or(1, 10);
    }

    @Test
    public void TestOrFalseFalse() {
        // False || False -> False
        Coverage.or(1, 11);
    }

    // And tests
    @Test
    public void testAndTrueTrue() {
        // True && True  -> True
        Coverage.and(0, 10);
    }

    @Test
    public void TestAndTrueFalse() {
        // True && False -> False
        Coverage.and(0, 11);
    }

    @Test
    public void TestAndFalseTrue() {
        // False && True -> False
        Coverage.and(1, 10);
    }

    @Test
    public void TestAndFalseFalse() {
        // False && False -> False
        Coverage.and(1, 11);
    }

    // caseStatement tests
    @Test
    public void testCaseStatement0() {
        // 0 -> 0
        Coverage.caseStatement(0);
    }

    @Test
    public void testCaseStatement1() {
        // 1 -> 1~4
        Coverage.caseStatement(1);
    }

    @Test
    public void testCaseStatement2() {
        // 2 -> 1~4
        Coverage.caseStatement(2);
    }

    @Test
    public void testCaseStatement3() {
        // 3 -> 1~4
        Coverage.caseStatement(3);
    }

    @Test
    public void testCaseStatement4() {
        // 4 -> 1~4
        Coverage.caseStatement(4);
    }

    @Test
    public void testCaseStatement5() {
        // 5 -> default
        Coverage.caseStatement(5);
    }

    // ifStatement tests
    @Test
    public void testIfStatement0() {
        // 0 -> 0
        Coverage.ifStatement(0);
    }

    @Test
    public void testIfStatement1() {
        // 1 -> 1~4
        Coverage.ifStatement(1);
    }

    @Test
    public void testIfStatement2() {
        // 2 -> 1~4
        Coverage.ifStatement(2);
    }

    @Test
    public void testIfStatement3() {
        // 3 -> 1~4
        Coverage.ifStatement(3);
    }

    @Test
    public void testIfStatement4() {
        // 4 -> 1~4
        Coverage.ifStatement(4);
    }

    @Test
    public void testIfStatement5() {
        // 5 -> default
        Coverage.ifStatement(5);
    }
}
```

## 図説

![Screenshot 2023-08-10 at 6.26.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130262/5e18a8f7-764e-46c7-5888-3b552f1ecfc8.png)

ここでは、orメソッドのみ説明します。

```java
    public static void or(int x , int y) {

        if (x == 0 || y <= 10) {
            System.out.println("true");
        } else {
            System.out.println("false");
        }
    }
```

C2（条件網羅）を満たすためには、変数xとyの判定がそれぞれ少なくとも1度はTrueおよびFalseになるようなテストケースを用意すれば十分です。つまり、以下の2つのテストケースでOKとなります。
x==0 : True, y<=10 : False
x==0 : False, y<=10 : True
ただし、この場合、else文に入るケースが存在しないため、C1の条件は満たされません。

実際にC1とC2の両方を満たす最低限のテストケースは、以下の3つです。  

x==0 : True, y<=10 : False
x==0 : False, y<=10 : True
x==0 : False, y<=10 : False

この状態ですと、JaCoCoは表示上All Greenになります。

### MCCまで満たす場合
MCC（複合条件網羅）も満たす場合には、すべての条件の組み合わせをテストする必要があります。したがって、以下の4つのテストケースが必要となります。
1. x==0 : True, y<=10 : True
2. x==0 : True, y<=10 : False
3. x==0 : False, y<=10 : True
4. x==0 : False, y<=10 : False

この場合にも、JaCoCoは表示上All Greenですが、1つ目と2つ目のテストケースが存在しない場合との区別は付きません。

構造ベースのテストでは、このor条件は1つ目と2つ目のケースが等価であると言えます（x == 0がTrueであれば次の条件を判定しないため）。しかし、仕様ベースのテストでは、MCCを満たさないと不十分という考え方もあります。

## まとめ
結論として、JaCoCoを使用して構造ベースのテストの網羅性を確認し、仕様ベースのテスト網羅性は別途担保するというアプローチが現実的であると考えられます。