この記事は公開されてから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.