Android studioでJacocoを使って、手動で一つずつテストケースを実行して、カバレッジレポートを作成する方法

Android studioでtestCoverageEnabledフラグをtrueにしたら、createDebugCoverageReportという機能が使えます。
しかしcreateDebugCoverageReportを実行したら、全てのUnit test / Instrumented testのテストケースが実行されます。環境に依存するテストケース（Internet、周辺機器など）がFailになってしまいます。

本文は手動でカバレッジレポートの作成方法を説明します。

#実行環境
・Android Studio 4.1.1
・compileSdkVersion 29
・Gradle version 6.5

#jacoco.gradleの作成
appあるいはmoduleのbuild.gradleと同じフォルダーにjacoco.gradleを作成します。

```jacoco.gradle
apply plugin: 'jacoco'
jacoco {
    toolVersion = "0.8.4"
    reportsDir = file("$projectDir/reports")
}

task jacocoTestReport(type: JacocoReport) {
    group = "Reporting"
    description = "Generate Jacoco coverage reports after running tests."
    reports {
        xml.enabled = true
        html.enabled = true
    }
    def coverageClassDirectories = fileTree(
            dir: "$buildDir/intermediates/javac/debug/classes",
            excludes: ['**/R*.class',
                       '**/*$InjectAdapter.class',
                       '**/*$ModuleAdapter.class',
                       '**/*$ViewInjector*.class'
            ])

    def coverageSourceDirs = [
            "$buildDir/../src/main/java"
    ]

    def coverageExecutionData = files("$projectDir/reports/coverageEC/coverage.exec")

    getClassDirectories().setFrom(coverageClassDirectories)
    getSourceDirectories().setFrom(coverageSourceDirs)
    getExecutionData().setFrom(coverageExecutionData)
}
```
coverageExecutionData は自分好きな所に設定してください。
後で手動でcoverage.execをこっちにコピーして、Android studioでcoverage.execを読み込んでレポートを生成します。

#build.gradleの設定

ファイルの上にjacoco.gradleのインポートを追加します。

```build.gradle
apply from: "jacoco.gradle"
```

debugの所に「testCoverageEnabled = true」を追加します。

```build.gradle
    buildTypes {
        //...
        debug {
            testCoverageEnabled = true
        }
    }
```

jacoco.gradleとbuild.gradleを変更したら一回「Sync Project with Gradle Files」を押してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951526/e6682866-55e7-9e6a-208e-e218fea57449.png)

GradleウインドウにjacocoTestReportが現れたはずです。
後で使うので、今放っておいてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951526/bea8f03b-6fe8-66e8-bfb8-51d8fac03837.png)

#クラスJacocoUtilsの制作
Instrumentation testの所(Android studioのデフォルトテスト例ExampleInstrumentedTest.java同じフォルダー)にJacocoUtils.javaを作成します。
後でInstrumentation testのテストケースで使います。

```JacocoUtils.java
package Example.package.name;

import android.content.Context;
import android.os.Environment;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

class JacocoUtils {
    static String TAG = "JacocoUtils";

    private static String DEFAULT_COVERAGE_FILE_PATH = "coverage.exec";

    /**
     * execファイルを生成する
     *
     * @param isnew execファイルを作り直すか。
     */
    public static void generateEcFile(Context context, boolean isnew) {
        Log.d(TAG, "カバレッジファイル生成: " + DEFAULT_COVERAGE_FILE_PATH);
        OutputStream out = null;
        File mCoverageFilePath = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS),DEFAULT_COVERAGE_FILE_PATH);

        try {
            if (isnew && mCoverageFilePath.exists()) {
                Log.d(TAG, "旧execファイルを削除");
                mCoverageFilePath.delete();
            }
            if (!mCoverageFilePath.exists()) {
                mCoverageFilePath.createNewFile();
            }
            out = new FileOutputStream(mCoverageFilePath.getPath(), true);

            Object agent = Class.forName("org.jacoco.agent.rt.RT")
                    .getMethod("getAgent")
                    .invoke(null);

            out.write((byte[]) agent.getClass().getMethod("getExecutionData", boolean.class)
                    .invoke(agent, false));

        } catch (Exception e) {
            Log.e(TAG, "generateEcFile: " + e.getMessage());
        } finally {
            if (out == null)
                return;
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
```

#ExampleInstrumentedTest.javaの変更
ExampleInstrumentedTest.javaにJacocoUtilsの使用を追加します。
テストが終わったらtearDownが実行されるので、必ずexecファイルが生成されます。

```ExampleInstrumentedTest.java
public class ExampleInstrumentedTest1{
    @After
    public void tearDown() throws Exception {
        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
        JacocoUtils.generateEcFile(appContext, false);
    }

    @Test
    public void ExampleInstrumentedTest1 () {
        //Do something to test
    }
}
```
#テストケースの実行
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951526/7883cf44-f971-1a16-cb94-7d98d0ed0b9f.png)

DebuggerのConsole画面でカバレッジファイル生成のメッセージが見えるはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951526/42b60759-7f94-0b34-46f2-966a81365a19.png)

＊もっとテストケースをしたらcoverage.execのサイズがだんだん増えていって、カバレッジも増えます。

#Android機器からcoverage.execをコピーする
USBでAndroid機器をPCと接続して、
「[Android機器名]\内部共有ストレージ\Android\data\\[package名]\files\Documents\coverage.exec」を
「[project名]\app\reports\coverageEC\」にコピーします。（存在しないフォルダーがあったら手動で生成）

#カバレッジレポートの生成
jacocoTestReportのRunを実行してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951526/f8520e48-a50e-b566-ce06-338f39c745cc.png)

「[Project名]\app\reports\jacocoTestReport\html」でカバレッジレポートが生成されるはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951526/b0c50d13-a714-fe1a-e77d-80a2162c21f2.png)

index.htmlを見てみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951526/9bd1a994-adb4-24de-6fcb-73bc611ff479.png)
できました！