LeakCanary + Robolectricでテストするときの注意



表題の環境にてテストを実行した時にテストが全コケなんて事になってしまったので、その解決の過程なんかをまとめてみます。

Androidでテストするとき、[Roboletlic](http://robolectric.org/ "Roboletlic")を使うとエミュレーターへの接続が無かったりして高速にできるし便利です。
ということで導入してちょこちょこテスト書きつつ品質上げていこうな！と思っていました。

さらにアプリの品質を向上させるため、メモリリーク発生時に検出してくれるクールな[LeakCanary](https://github.com/square/leakcanary "LeakCanary")を導入してみました。

Robolectricはこの辺りや
http://qiita.com/yuya_presto/items/d5cc27225a19e1971096

LeakCanaryはこの辺を
http://qiita.com/rejasupotaro/items/4a8cfe0abda2d83145dd

参考にさせていただきました。ありがとうございます。

さて本題ですが、前述のとおり上記環境においてテスト全コケしてしまいました。

## 何が起こったのか
テスト全コケのそれ以上でもそれ以下でもないですが、それだけだと雑なので…
**特定のBuildVariantsにおいて、Roboletlicでテストを走らせる際にLeakCanaryのインストールに失敗していたというのが原因になります。**

## どういうことですか
LeakCanaryを使用する際はApplicationClassに一行
`LeakCanary.install(this);`
としてあげれば済むのですが、これは端末にLeakというアプリを同時にインストールすることになります。

なので、リリースバージョンやテスターさんに配布するバージョンにおいては、このLeakアプリは見せたくないですよね。
ですので、gradleに以下の様な感じでdependenciesを書いてあげることになります。

```
betaCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
```

この状態でテストを回すと、betaCompileでのテストだけこけてしまいます。

## とりあえずサンプルプロジェクトを作ってみました。
LeakCanaryやRoboletlicの導入なんかに関しては省略させていただきます。

プロジェクトはこちら。
https://github.com/wakwak3125/LeakCanaryTest

まずはApplicationクラスを継承した、MyApplicationです。

```android:MyApplication
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        LeakCanary.install(this);
    }
}
```


次に、テストです。テストケースはそのままにしてあります。

```android:ExampleUnitTest
@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21, packageName = "com.wakwak.leakcanarytest")
public class ExampleUnitTest {
    @Test
    public void addition_isCorrect() throws Exception {
        assertEquals(4, 2 + 2);
    }
}
```

build.gradleはこんな感じ。

```build.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.wakwak.leakcanarytest"
        minSdkVersion 16
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {

        beta {
            minifyEnabled false
        }

        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    testCompile 'org.robolectric:robolectric:3.0'

    compile 'com.android.support:appcompat-v7:23.1.1'
	
    betaCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta1'
    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
}

```

でUnitTestを実行するコマンドが
リリースバージョンが`./gradlew testReleaseUnitTest`
ベータバージョンが`./gradlew testBetaUnitTest`

となります。では実行してみましょうまずは、Releaseから。

```
./gradlew testReleaseUnitTest                                                

:app:preBuild UP-TO-DATE
:app:preReleaseBuild UP-TO-DATE
:app:checkReleaseManifest
:app:preBetaBuild UP-TO-DATE
:app:preDebugBuild UP-TO-DATE
:app:prepareComAndroidSupportAppcompatV72311Library UP-TO-DATE
:app:prepareComAndroidSupportSupportV42311Library UP-TO-DATE
:app:prepareComSquareupLeakcanaryLeakcanaryAndroidNoOp14Beta1Library UP-TO-DATE
:app:prepareReleaseDependencies
:app:compileReleaseAidl UP-TO-DATE
:app:compileReleaseRenderscript UP-TO-DATE
:app:generateReleaseBuildConfig UP-TO-DATE
:app:generateReleaseAssets UP-TO-DATE
:app:mergeReleaseAssets UP-TO-DATE
:app:generateReleaseResValues UP-TO-DATE
:app:generateReleaseResources UP-TO-DATE
:app:mergeReleaseResources UP-TO-DATE
:app:processReleaseManifest
:app:processReleaseResources
:app:generateReleaseSources
:app:processReleaseJavaRes UP-TO-DATE
:app:compileReleaseJavaWithJavac
:app:preReleaseUnitTestBuild UP-TO-DATE
:app:prepareReleaseUnitTestDependencies
:app:processReleaseUnitTestJavaRes UP-TO-DATE
:app:compileReleaseUnitTestJavaWithJavac
:app:compileReleaseUnitTestSources
:app:mockableAndroidJar UP-TO-DATE
:app:assembleReleaseUnitTest
:app:testReleaseUnitTest

BUILD SUCCESSFUL

Total time: 13.652 secs

```

成功ですね。

次に、Betaのテストを走らせます。

```
./gradlew testBetaUnitTest

:app:preBuild UP-TO-DATE
:app:preBetaBuild UP-TO-DATE
:app:checkBetaManifest
:app:preDebugBuild UP-TO-DATE
:app:preReleaseBuild UP-TO-DATE
:app:prepareComAndroidSupportAppcompatV72311Library UP-TO-DATE
:app:prepareComAndroidSupportSupportV42311Library UP-TO-DATE
:app:prepareComSquareupLeakcanaryLeakcanaryAndroid14Beta1Library UP-TO-DATE
:app:prepareBetaDependencies
:app:compileBetaAidl UP-TO-DATE
:app:compileBetaRenderscript UP-TO-DATE
:app:generateBetaBuildConfig UP-TO-DATE
:app:generateBetaAssets UP-TO-DATE
:app:mergeBetaAssets UP-TO-DATE
:app:generateBetaResValues UP-TO-DATE
:app:generateBetaResources UP-TO-DATE
:app:mergeBetaResources UP-TO-DATE
:app:processBetaManifest UP-TO-DATE
:app:processBetaResources UP-TO-DATE
:app:generateBetaSources UP-TO-DATE
:app:processBetaJavaRes UP-TO-DATE
:app:compileBetaJavaWithJavac UP-TO-DATE
:app:preBetaUnitTestBuild UP-TO-DATE
:app:prepareBetaUnitTestDependencies
:app:processBetaUnitTestJavaRes UP-TO-DATE
:app:compileBetaUnitTestJavaWithJavac UP-TO-DATE
:app:compileBetaUnitTestSources UP-TO-DATE
:app:mockableAndroidJar UP-TO-DATE
:app:assembleBetaUnitTest UP-TO-DATE
:app:testBetaUnitTest

com.wakwak.leakcanarytest.ExampleUnitTest > addition_isCorrect FAILED
    java.lang.RuntimeException
        Caused by: java.lang.NullPointerException

1 test completed, 1 failed
:app:testBetaUnitTest FAILED

FAILURE: Build failed with an exception.

```

## というわけで解決策

ということで解決方法なんですが、これが正解かどうかはわかりませんが、`Multidex`を有効にした際にテストを走らすときのエラーと似ているような感じがしたので、MyApplicationクラスを以下のように変更しました。

```android:MyApplication
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        try {
            LeakCanary.install(this);
        } catch (Exception ignore) {
            
        }
    }
}
```

`LeakCanary.install(this);`をtry~catchで囲んであげるだけです。

こうすることによって上記のエラーを回避することができます。
releaseCompileではなぜ上記のようなエラーが起きないかといいますと、LeakCanaryはno-opの際はLeakアプリをインストールしないからだと思われます。（この辺ちゃんとソース読むと解決すると思いますが、さっと見た感じ何もしていないようでした。）

というわけで、LeakCanaryを有効にし、Roboletlicでテストを走らせる際は上記のような対応をしてあげると良いと思われます。

もし私の勘違いや間違いなどアレば指摘していただけますと幸いです。

今回の件はアタリマエのことかもしれませんが、最初は面食らってしまったのでまとめてみました。

それでは…