
Android Local TestでAndroid固有のクラスをモック化する
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
まえがき
AndroidでUnit TestsでAndroid固有のクラス、メソッドを使用していると以下の様なエラーができます。
java.lang.RuntimeException: Method d in android.util.Log not mocked. See http://g.co/androidstudio/not-mocked for details.
Unit Testsで、テストするコード内にAndroidのクラスであるLog.d()を使用している時のエラーです。
いちいちLogのエラーのために、テスト時にコメントアウトするのも面倒くさいですし、テストしくにくいからLogは書かないようにするのも、デバックがしにくく開発速度に影響します。
どうにかLogクラスをテストの時に実行されない、またはエラーにならない、モック化したいと思います。
Logのモック化
先人のQiitaの記事に1つ加え(一番上の記事)、実行速度比較をしてみます。
JavaのClassLoaderの仕組みを上手くつかう
ロジック部分のテストを行うために、本来junitだけ事が足りるのに
Logのためだけに、わざわざ大きめのテストライブラリーをいれるのは大げさな気がします。
JavaのClassLoaderの仕組みをうまく使うことでLogクラス程度であれば、なんのライブラリーも入れずにMock化することできます。
同じパッケージに同じクラスがあった場合どっちがロードされるか。。。
ロードの順番で先に読み込まれたクラスがロードされれます。
つまり、ライブラリー先にロードされるので、Androidのクラスを上書きすることができます
具体的にかくと、AndroidのLogと同じパッケージでLogクラス作成します。
上書きされるので、インターフェイスは同じにしておきます。
(Log.dしか使ってない場合はdだけあれば大丈夫です)
テスト時にだけ上書きしてほしいので(project root)/app/src/test/javaに起きます。
package android.util;
public class Log {
    public static int v(String tag, String msg) {
        return 0;
    }
    public static int v(String tag, String msg, Throwable tr) {
        return 0;
    }
    public static int d(String tag, String msg) {
        return 0;
    }
    public static int d(String tag, String msg, Throwable tr) {
        return 0;
    }
    public static int i(String tag, String msg) {
        return 0;
    }
    public static int i(String tag, String msg, Throwable tr) {
        return 0;
    }
    public static int w(String tag, String msg) {
        return 0;
    }
    public static int w(String tag, String msg, Throwable tr) {
        return 0;
    }
    public static int w(String tag, Throwable tr) {
        return 0;
    }
    public static int e(String tag, String msg) {
        return 0;
    }
    public static int e(String tag, String msg, Throwable tr) {
        return 0;
    }
}
以下の3つの方法は、Qitaで解説があります。
[Method d in android.util.Log not mocked.に対する3つの対処法 - Qiita] (http://qiita.com/oxsoft/items/b12b7a6c17e92eb880cd)
- testOptionsを設定する
- Robolectricを使う
- PowerMockを使う
速度比較
測定対象
1.Android Studio 2.1でEmpty Projectを作ります
apply plugin: 'com.android.application'
android {
    compileSdkVersion 24
    buildToolsVersion "24.0.0"
    defaultConfig {
        applicationId "com.classmethod.logtest"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.0.0'
}
2.Logを使った適当なクラスを作ります (project root)/app/src/main/java/(app pkg)/Calclation.java
public class Calculation {
    public int sum(int x, int y) {
        Log.d("sum", x + "+" + y);
        return x + y;
    }
}
3.基本となるテストコード
(project root)/app/src/test/java/(app pkg)/CalclationTest.java
public class CalculationTest {
    @Test
    public void 加算のテストをする() {
        Calculation calculation = new Calculation();
        Assert.assertThat(calculation.sum(7, 7), Is.is(14));
    }
}
4.テスト実行 ./gradlew clean test
1回目の値はすてて、2回目移行のTotal timeの3回実行したときの平均を比較する
比較
- Java ClassLoader式: 19.775 secs
- testOptions式: 19.355 secs
- Robolectric式: 25.303 secs
- PowerMock式: 21.613 secs
まとめ
シンプルなコードためあんまり差がでませんでしたが、おすすめクラスロード式です。
理由は、以下のとおりです。
- 一度Logクラスをつくれば、テストコードで書かなくて良い
- ライブラリーをいれなくてよい
- モックする時間を短縮できる
- 早い
- Log以外の部分でエラーになるようだったら、設計がよくない可能性。
皆さんもぜひ試してみてください。
参考文献
- ClassLoader (Java Platform SE 6)
- Method d in android.util.Log not mocked.に対する3つの対処法 - Qiita
- Building Local Unit Tests | Android Developers
- robolectric/robolectric: AndroidUnit Testing Framework
- jayway/powermock: PowerMock is a Java framework that allows you to unit test code normally regarded as untestable.















