Robolectric 3.0 から Robolectric 3.1 へのアップデートガイド

# 概要

つい先日、Robolectric 3.1がリリースされました。Robolectric 3.0がリリースされたのは2015年7月で、Robolectric 3.1のリリースは2016年6月ですから、ほぼ1年ぶりのアップデートとなりました。
主な[アップデート内容](https://github.com/robolectric/robolectric/releases)はLollipop-MR1およびMarshmallowサポート、Play ServicesのShadowクラスの改善などがあるようです。

[公式のアップデートガイド](https://github.com/robolectric/robolectric/wiki/3.0-to-3.1-Upgrade-Guide)によると、`ShadowApplication`クラスの機能が`RuntimeEnvironment.application`に置き換えられるようですが、実際にはこれ以外にも多くの変更が入っており、おそらくこのガイドにある変更だけでは不足することが出てくるでしょう([3.0から3.1へのdiff](https://github.com/robolectric/robolectric/compare/robolectric-3.0...robolectric-3.1?diff=unified&name=robolectric-3.1)を出してみましたが、GitHub上では大きすぎて一部のdiffしか閲覧できません😢)。

この記事には、アップデートに際して公式ガイドにはない部分で修正が必要な箇所について記載しています。

# `sdk = 23`に伴う修正

これによって、MarshmallowのAPIに依存する部分のテストも出来るようになります。Robolectric 3.0まではLollipopが最新のサポートバージョンでしたので、API Levelをチェックするif文があるメソッドをテストしている場合、いくらかの変更が必要になります。

```java:テスト対象
public class TestTarget {
  private Something something;

  public TestTargeT(Something something) {
    this.something = something;
  }

  public boolean hoge() {
    if (Build.VERSION.SDK_IN >= Build.VERSION_CODES.LOLLIPOP_MR1) {
      return something.checkHogeApi22();
    }
    return something.checkHoge();
  }
}
```

```java:3.0時点でのテスト
@Config(constants = BuildConfig.class, sdk = 21)
@RunWith(RobolectricGradleTestRunner.class)
public class HogeTest {
  private TestTarget target;
  private Something mockSomething;

  @Before
  public void setUp() throws Exception {
    mockSomething = // mockito とかでモックする;
    target = new TestTarget(mockSomething);
  }

  @Test
  public void hoge_fallback() throws Exception {
    target.hoge();
    // mockito とかで Something#checkHoge が呼ばれたことを確認する等
  }
}
```

3.0時点でのテストでは`sdk = 21`ですので、hogeメソッドの`if`の条件文は`false`になりそのまま`checkHoge()`メソッドが呼ばれます。これを3.1にアップデートして`sdk = 23`に修正すると、呼ばれるのは`checkHogeApi22()`メソッドになるので、テストが失敗するようになります。

# `NoClassDefFoundError: javax/microedition/khronos/opengles/GL`対応

おそらくRobolectric 3.0に上げた時点でもプロジェクトによっては発生していたかもしれませんが、自分のプロジェクトでは3.1に上げたところで発生しました。

Viewがテストに絡んでくると起こる問題で、[Robolectricがjavax配下のパッケージ群をまるまるロードしないようにしている](https://github.com/robolectric/robolectric/issues/1932)ことが原因のようです。Robolectricのイシューには、以下のように依存関係を`build.gradle`に追加せよとあります。実際これでうまく動作するようになります。

```groovy:build.gradle

dependencies {
  // ...
  testCompile 'org.khronos:opengl-api:gl1.1-android-2.1_r1'
}

```

# テストが遅い

テストの実行が目に見えて遅くなります。弊社のテストケースは750ほどありますが、単純にテストを実行するだけのコマンドを叩いた時に、3.0まではおよそ2分ほどで完了していたものが、3.1で6〜7分ほどかかるようになってしまいました。

```:テスト時のコマンド
$ ./gradlew testDebugUnitTest
```

[提出されたプルリクエスト](https://github.com/robolectric/robolectric/pull/2508)によると、`ShadowWrangler#methodInvoked()`の処理でキャッシュをコレクションから読み出す部分に問題があるようですが、どうもそれだけが問題では無いようです。

弊社の場合、以前`AndroidManifest.xml`がテストランナーから見えない問題の解消のために、独自に`RobolectricGradleTestRunner`を拡張していました。

最近になって、`@Config`に指定する内容は`robolectric.properties`というファイルに書き出せることを知り、かつ`AndroidManifest.xml`の場所もそのファイルの中で管理できるということで、全てのテストの共通する`@Config`を`robolectric.properties`に置き換え、不要になった独自拡張のテストランナーを`RobolectricGradleTestRunner`に戻しました。

そうすると、コマンドの実行が3分ほどで終わるようになりました。

`robolectric.properties`は次のように記述します。

```robolectric.properties
sdk=23
manifest=src/main/AndroidManifest.xml
packageName=com.example.debug
constants=com.example.BuildConfig
application=com.example.TestSampleApplication
```

パッケージ名や`constants`、`application`などは適宜読み替えてください。
このファイルは`$app_module/src/test/resources/robolectric.properties`に配置します。

そもそも何故遅くなるのかと言う部分については、Robolectric 側のコミットログを見てみないことにはわからないため、まだ調査中です。ただ少なくとも、`RobolectricGradleTestRunner`の拡張を`@RunWith`で指定すると、何らか実行速度に影響が出るようではあります。
なにか分かったことがあれば追記してみたいと思います。