Robolectricのハマりポイント

Toru Hosokawa
twitter:@anton0825
facebook:toru.hosokawa1
github:hosokawa0825
blog:http://d.hatena.ne.jp/anton0825/

2015.2.28

Robolectric

みんな大好き単体テストライブラリ。
JVM上でテストを素早く実行できて便利!だけど、落とし穴も色々ある・・・
テストを書いていてハマったポイントを紹介します。

ドキュメントが薄い

公式ドキュメントにはかなり基本的なことしか書いてない。
サンプルプロジェクトにサンプルコードがほとんどない

最も頼りになるドキュメントは、本家プロジェクトのテストコード
例:RobolectricTestRunnerのテストコード
後はRobolectric本体のコードを読んで理解する。

assetsフォルダ・resourcesフォルダを複数設定する

動機はサーバからのレスポンスなどのテストデータを別ファイルにして/src/test/assetsに置くため。
一方で/src/main/assetsも読み込める状態を維持したい。


対応方法

  • RobolectricTestRunnerのサブクラスを作り、ResourceLoaderをカスタマイズする
    サンプルコード
  • テストクラスの@RunWithでMyTestRunnerを指定する

問題点

  • こんなdirty hackな方法はつらい・・・
  • Robolectricの内部実装に依存しているのでRobolectricがバージョンアップしたら動かなくなる可能性高い。

戻り値をテストデータで置き換えるのが難しい場合がある

例:Build.VERSION.RELEASE(OSのバージョン番号)の値を書き換えたい。
が、宣言時に初期化されている。かつstatic finalなので書き換えられない。


                    public static final String RELEASE = getString("ro.build.version.release");

                    private static String getString(String property) {
                        return SystemProperties.get(property, UNKNOWN);
                    }
                
解決策:カスタムTestLifecycleを定義してBuild.VERSION.RELEASEが初期化される前にShadowSystemPropertiesを書き換えた。 サンプルコード

Fragmentのテストがつらい

そもそもMVCのControllerは色んなクラスに依存していることが多いのでテストはやりづらい。
Fragmentのテストコード例:


                    // 事前処理
                    OrderListFragment target = OrderListFragment_.builder().build();
                    Robolectric.addPendingHttpResponse(200, TestUtils.getStringFromAssets(TestDataType.HTTP, "orderList.response.json"));

                    // 処理実行
                    startFragment(target, MockMyActivity.class);
                    Robolectric.runBackgroundTasks();
                    Robolectric.runUiThreadTasks();

                    // 検証
                    List<Order> orders = (List<Order>) Whitebox.getInternalState(target, "orders");
                    assertThat(orders.size()).isEqualTo(6);
                

FragmentからActivityへのcallbackをインターフェース経由でやっていればActivityのMockは作りやすい。
ActivityのMockの例:


                    public class MockMyActivity extends FragmentActivity implements IErrorHandler {
                        @Override
                        public void onError(final Exception e) {
                            Log.d("MockMyActivity", "onError", e);
                        }
                    }
                

Android StudioのUnit Test Support

公式サイト

Robolectricと同じアプローチに見える。(JVMで実行、Android SDKのクラスはMockに置き換え)
Shadowがまだないので現状使えない。が、いずれRobolectricを置き換えるようになり、テストコードの書き直しが必要になるかも。。
一旦様子見で。

まとめ

  • ちょっと高度なことをやろうとするとすぐハマる
  • でも単純なテストは非常に快適に書ける
  • 使いどころを見極めて費用対効果の高いテストコードを書きましょう