LoginSignup
3
1

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

Last updated at Posted at 2023-08-16

この記事の目的

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)について
に沿って定義しておきます。

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

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

サンプルコード

Target Code (Testee)

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)

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

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

    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を使用して構造ベースのテストの網羅性を確認し、仕様ベースのテスト網羅性は別途担保するというアプローチが現実的であると考えられます。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1