テストクラスをAndroid Instrumentation TestsとRobolectric Tests両方で走らせる

(追記: このハックはライブラリとして独立させました gfx/RobolectricInstrumentation · GitHub

Robolectricは便利ですが、Oracle JREとAndroid Runtimeの微妙な挙動の違い、特にAndroid Runtimeにバグの回避するようなコードのテストができないという問題があります。

ところでRobolectricを純粋にJVM用のAndroid Frameworkランタイムとみなすと、テストクラスをほとんどAndroid Instrumentation Testsと同じように書くことができます。

Robolectricのほうがテストの実行が速いので、開発中はRobolectricを使うほうが効率はいいのですが、微妙に挙動に違いがあるので実機でもテストは走らせたいところです。そこで同じテストクラスをAndroid Instrumentation TestsとRobolectric Tests両方で走らせられると、RobolectricとInstrumentation Testsの双方のメリットを享受できます。そう考えて工夫した結果、Robolectric用にダミーのAndroid Testing Frameworkを実装することでうまくいきました。

ダミーのAndroid Testing Frameworkといっても、追加したのは AndroidJUnit4InstrumentationRegistry だけです。同時に、もともとあったテストクラスからrobolectric依存をなくし、Android Testing Frameworkに移植しました。つまり全体としてはRobolectric APIで実装したAndroid Test Frameworkのラッパーを作った形です。

なお、逆のパターン、つまりAndroid Testing Framework APIで実装したRobolectricのラッパーも試してみたのですが、Android Testing Frameworkの一部が android.support.test.runner.AndroidJUnit4 のクラスインスタンスそのものに依存していてラッパーを受け付けなかったので断念しました。

結果、コードはこのような形になりました。

hack to run tests as Android Instrumentation Tests by gfx · Pull Request #97 · gfx/Android-Orma · GitHub

今回のケースではAndroid Testing FrameworkやRobolectricの機能をほとんど使っていなかったので、テストコード自体の書き換えはほとんど不要でした。テストの実行は ./gradlew test が40秒、 ./gradlew connectedAndroidTest が1分30秒なので、開発中はRobolectricでテストをし、ときどき(あるいはCIで) ./gradlew connectedAndroidTest で確認するというのがよさそうです。