Robolectricの@Configを共通化する方法


Robolectricの`@Config`アノテーションで指定する各種設定を
共通化させる方法を紹介します。

(この内容は[Android Testing Bootcamp #2](http://android-testing-bootcamp.connpass.com/event/31636/)で、[発表した内容](https://speakerdeck.com/egugue/robolectricfalse-at-configwogong-tong-hua-surufang-fa)と同じですが、
そこで説明しきれなかった補足も付け加えています。)

## 前提条件
この記事は、Robolectric3.1を使用した際の説明となっています。
ただしRobolectric3.0でも、少しコードを修正すれば適用できる内容となります。
[このサンプルプロジェクト](https://github.com/egugue/RobolectricConfigShare)で、3.1と3.0それぞれに対応したコードを載せています。

## 一般的なRobolectricの使い方
一般的なRobolectircの使い方は以下のとおりだと思います。

1. `@RunWith`に`RobolectricGradleTestRunner`を指定
2. `@Config`に必要な各種設定を指定

```java:ExampleUnitTest.java
@RunWith(RobolectricGradleTestRunner.class)
@Config(
  sdk = 23,
  constants = BuildConfig.class
)
public class ExampleUnitTest {

  @Test public void addition_isCorrect() throws Exception {
    assertEquals(4, 2 + 2);
  }

}
```


## 問題点
ただし上記のような、テストクラスで`@Config`の各設定を指定するという、一般的な方法を用いると
以下の様な問題が後々起こる可能性が出てきます。

1. テストクラスを作成するたびに`@Config`を指定しなくてはならず面倒
2. 指定したすべての`@Config`の設定を一括で変更したい場合、 <br>すべてのテストクラスを修正しなくてはいけない
3. Robolectricがバージョンアップし、その結果`@Config`の設定方法が変更になった場合、<br>すべてのテストクラスを修正しなくてはいけない

以上の問題から、`@Config`の設定を共通化できるものは、できるだけしておきたいです。

## 共通化する2つの方法
以上を踏まえ、`@Config`の設定を共通化する方法を、2つ紹介します。

### その1 - ファイルに切り出す
この方法は、[公式で提供している機能](http://robolectric.org/configuring/)を使います。
![image](https://qiita-image-store.s3.amazonaws.com/0/56580/d1754ad8-2d37-00d2-1682-6b8c518ec8e4.png)

やり方としては、以下のとおりです。
1. robolectric.propertiesというファイルを作る
2. このファイルに、共通化させたい`@Config`の設定を記述する
3. このファイルを、`app/src/test/resources`ディレクトリに置く

```text:app/src/test/resources/robolectric.properties
sdk=23 
constants=my.package.BuildConfig
```

#### 結果
このテストランナーを用いたテストコードは以下になります。
これにより、`@Config`アノテーションを指定せずに、共通化したい`@Config`の設定を指定できます。

```java:ExampleUnitTest.java
@RunWith(RobolectricGradleTestRunner.class)
public class ExampleUnitTest {

  @Test public void addition_isCorrect() throws Exception {
    assertEquals(4, 2 + 2);
  }

}
```


### その2 - テストランナーを作成する
この方法では、`@Config`の設定を共通化できるテストランナーを作ります。

```java:RobolectricCustomRunner.java
public class RobolectricCustomRunner extends RobolectricGradleTestRunner {
  private static final int[] SDK = new int[]{23};

  public RobolectricCustomRunner(Class<?> klass) throws InitializationError {
    super(klass);
  }

  @Override public Config getConfig(Method method) {
    // 実際のテストクラスで指定、生成されたConfigを取得
    Config c = super.getConfig(method);

    // Configの設定を書き換えたものを登録する
    return new Config.Implementation(
        sdkLevel,
        c.manifest(), c.qualifiers(), c.packageName(),
        c.abiSplit(), c.resourceDir(), c.assetDir(),
        c.buildDir(), c.shadows(), c.instrumentedPackages(),
        c.application(), c.libraries(),
        constants
    );
  }

  // Config#sdkを指定していなかった場合は23に
  private static int[] pickSdkLevel(int[] sdkArray) {
    return sdkArray.length == 0 ? SDK : sdkArray;
  }

  private static Class<?> pickConstants(Class<?> constants) {
    return constants == Void.class ? BuildConfig.class : constants;
  }

}
```

#### 結果
このテストランナーを用いたテストコードは以下になります。
これにより、`@Config`アノテーションを指定せずに、共通化したい`@Config`の設定を指定できます。

```java:ExampleUnitTest.java
@RunWith(RobolectricCustomRunner.class)
public class ExampleUnitTest {

  @Test public void addition_isCorrect() throws Exception {
    assertEquals(4, 2 + 2);
  }

}
```

## どちらの方法を採用するべきか
これは__私個人の意見__となりますが、以下の理由により、
テストランナーを用いた方法を採用したほうがいいと考えています。

1. コードで設定を記述できるので、柔軟に指定できる。<br>(例えば各BuildVariantで違う設定をする)
2. 今後テストランナーを使ってRobolectricのカスタマイズをする必要が出てきた時、<br>その設定と`@Config`の設定を一元管理できる
3. ファイル、テストランナー、どちらの方法でもテストクラス作成時のコード量は変わらない。<br>(どちらも`@RunWith`でテストランナーを指定する)

## 補足
テストクラスに`@Config`を絶対に書かないほうが良いというわけではなく、
Robolectricを使うテストクラスで__常に指定する`@Config`の設定__を出来る限り共通化したほうがいいのではないかというのが、
この記事の伝えたいことになります。

## 資料
- [Speacker Deck](https://speakerdeck.com/egugue/robolectricfalse-at-configwogong-tong-hua-surufang-fa)
- [サンプルプロジェクト](https://github.com/egugue/RobolectricConfigShare)

## 参考
- [Configuring Robolectric](http://robolectric.org/configuring/)
- [Migrating to Robolectric v3 a.k.a Just Work, Dear God, Just Work!](http://evendanan.net/robolectric/unit-test/2015/04/09/migrating-to-robolectric-v3/)