Robolectric に対応したプロジェクトを作る

今更ながら、[この記事](http://www.peterfriese.de/android-testing-with-robolectric/)の通りに、Robolectric に対応したプロジェクトを作る手順をまとめておきます。

## Robolectric の役割

Android は、標準のフレームワークにテストフレームワークも含んでいます。
JUnit3 をベースとして、モジュールのテストや画面のテストの為のフレームワークが提供されています。

一方、実行環境の制約から、Android エミュレータや端末上での実行が要求されます。テストの実行のためにエミュレータを立ち上げたり、テストの apk をインストールしたりする必要が有るため、テストそのものの実行に手間と時間がかかってしまいます。

Robolectric では、Android のフレームワークにある各種の API を JVM 上でエミュレートすることにより、テストの実行時間を短くスピーディにすることができます。

## 手順の概要

1. build.gradle の設定でテストの準備をする
2. ~~TestRunner を拡張して、AndroidManifest を見えるようにする~~
3. テストを書く

## build.gradle の準備

ビルドスクリプトに記述する内容は以下の通りです。

1. ~~android-test プラグインの導入~~
2. robolectric プラグインの導入
2. JUnit4, Robolectric, Android-FEST への依存の宣言
3. ~~テスト用のディレクトリの変更~~

~~現在、Robolectric は KitKat に対応していないため、TargetSDK と CompileWith の API Level を 18 にしておきます。~~
この設定は`@Config`アノテーションを用いてテストコード中で設定できます。

**2015/03/10 追記：以下のスクリプトはすでに古く、動作する保証がありません。現時点で最新の記述はこの下にあります。**

```groovy
buildscript {
    // ビルドスクリプトの依存
    repositories {
        mavenCentral()
        maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.8.+'
        classpath 'com.squareup.gradle:gradle-android-test-plugin:0.9.1-SNAPSHOT'
    }
}

repositories {
    mavenCentral()
    maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}

apply plugin: 'android'
apply plugin: 'android-test'

android {
    compileSdkVersion 18
    buildToolsVersion "19.0.1"

    defaultConfig {
        minSdkVersion 7
        targetSdkVersion 18
        versionCode 1
        versionName "1.0"
    }

    sourceSets {
        instrumentTest.setRoot('src/test')  // テストディレクトリを test に変更する
    }
}

dependencies {
    compile 'com.android.support:gridlayout-v7:19.0.1'
    compile 'com.android.support:support-v4:19.0.1'
    compile 'com.android.support:appcompat-v7:19.0.1'
    testCompile 'junit:junit:4.11' // JUnit 4
    testCompile 'org.robolectric:robolectric:2.3-SNAPSHOT'  // Robolectric 本体
    testCompile 'com.squareup:fest-android:1.0.+' // FEST(Fixtures for Easy Software Testing) を Android で利用するためのライブラリ
    instrumentTestCompile 'junit:junit:4.11'
    instrumentTestCompile 'org.robolectric:robolectric:2.3-SNAPSHOT'
    instrumentTestCompile 'com.squareup:fest-android:1.0.+'
}
```

**2015/03/10 追記：こちらが最新**

```groovy
buildscript {
    // ビルドスクリプトの依存
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.1.0'
        classpath 'org.robolectric:robolectric-gradle-plugin:1.0.1'
    }
}

repositories {
    jcenter()
}

apply plugin: 'com.android.application'
apply plugin: 'org.robolectric' // 少し前まで robolectric でしたが、正式リリース以後 org.robolectric となります

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.2"

    defaultConfig {
        minSdkVersion 7
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"
    }
    // robolectric のテストは androidTest ディレクトリに放り込むので sourceSet について特に記述なし
}

dependencies {
    compile 'com.android.support:gridlayout-v7:19.0.1'
    compile 'com.android.support:support-v4:19.0.1'
    compile 'com.android.support:appcompat-v7:19.0.1'
    // 以下、様々な transitive dependencies を exclude します
    androidTestCompile('junit:junit:4.11') { // JUnit4
        exclude module: 'hamcrest-core'
    }
    androidTestCompile('org.robolectric:robolectric:2.4') { // Robolectric 本体
        exclude module: 'classworlds'
        exclude module: 'commons-logging'
        exclude module: 'httpclient'
        exclude module: 'maven-artifact'
        exclude module: 'maven-artifact-manager'
        exclude module: 'maven-error-diagnostics'
        exclude module: 'maven-model'
        exclude module: 'maven-plugin-registry'
        exclude module: 'maven-profile'
        exclude module: 'maven-project'
        exclude module: 'maven-settings'
        exclude module: 'nekohtml'
        exclude module: 'plexus-container-default'
        exclude module: 'plexus-interpolation'
        exclude module: 'plexus-utils'
        exclude module: 'wagon-file'
        exclude module: 'wagon-http-lightweight'
        exclude module: 'wagon-http-shared'
        exclude module: 'wagon-provider-api'
        exclude group: 'com.android.support', module: 'support-v4'
    }
    androidTestCompile 'org.mockito:mockito-all:1.9.5'
    androidTestCompile 'com.squareup:fest-android:1.0.+' // FEST(Fixtures for Easy Software Testing) を Android で利用するためのライブラリ
}
```

## ~~TestRunner の準備~~

~~`src/test/java`にテストを配置する関係で、Robolectric が AndroidManifest を見つけられなくなるため、TestRunner を拡張して場所を指定します。~~

**2015/03/10 追記：以下の TestRunner は必要無くなりました。org.robolectric.RobolectricTestRunner を使用します。AndroidManifest については、`@Config`アノテーションを用います。**

```java
import org.junit.runners.model.InitializationError;
import org.robolectric.AndroidManifest;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.res.Fs;

public class RobolectricGradleTestRunner extends RobolectricTestRunner {
    public RobolectricGradleTestRunner(Class<?> testClass) throws InitializationError {
        super(testClass);
    }

    @Override
    protected AndroidManifest getAppManifest(Config config) {
        String manifestProperty = System.getProperty("android.manifest");
        if (config.manifest().equals(Config.DEFAULT) && manifestProperty != null) {
            String resProperty = System.getProperty("android.resources");
            String assetsProperty = System.getProperty("android.assets");
            return new AndroidManifest(Fs.fileFromPath(manifestProperty), Fs.fileFromPath(resProperty),
                    Fs.fileFromPath(assetsProperty));
        }
        AndroidManifest appManifest = super.getAppManifest(config);
        return appManifest;
    }

}
```

以上で、プロジェクトの準備は完了です。

## テストの記述

Android フレームワークが提供するテストスートと違い、JUnit4 が使えるので、以下のようにテストが記述できます。

```java
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;

import static org.junit.Assert.assertTrue;

@RunWith(RobolectricTestRunner.class) // TestRunner の指定
@Config(manifest = "src/main/AndroidManifest.xml", emulateSdk = 18) // sdk の api level を指定する
public class MyModuleTest {
    @Test // テストケースの表明
    public void shouldPass() throws Exception {
        assertTrue(true);
    }
}
```

テストの実行は以下のようにします。

`$ ./gradlew test`

これで、エミュレータ無しにテストが実行できるようになりました。