Android のテストで設計をイイカンジにしよう

More than 5 years have passed since last update.

Android フレームワークは、あまり強力とはいえないものの、ある程度のテストフレームワークを内包しています。

ベースが JUnit3 なので、記法が古かったりすることもありますが、とりあえず JUnit3 の作法を身に付ければある程度のテストは書くことが出来ます。


Android のテストケースフレームワーク

Android がもつ各種のコンポーネントをテストするためのフレームワークが有ります。

いろいろな種類がありますが、おそらく最も頻繁に使うのはAndroidTestCaseでしょう。


AndroidTestCase

Android でユニットテストをするなら、ほぼ間違いなく使うであろうクラスです。

そのようなテストケースでは、すべてこのクラスを基底に作っていきます。

もう少し具体化して言うと、Contextに依存するすべてのテストはこのテストクラスを使うことで、イイカンジにテストができるようになります。つまり、テスト用に使うContextをこのAndroidTestCaseがよしなに管理してくれるわけです。


InstrumentationTestCase

Android フレームワークにおけるもう一つの基底テストクラスです。

こちらは機能テストのためのもので、ボタンを押す、とか、テキストを入力する、と言ったことをテストするために使います。

しかし、何かとクセの強いフレームワークで、かつ何かと出来ないことも多いため、このクラスを純粋に使うことはめったにないでしょう。今や、Robotium というもう少しリッチに機能テストが書けるライブラリもあることですし…

通常、このクラスのサブクラスであるActivityInstrumentationTestCase2を用いて機能テストを作ります。


ActivityTestCase

Activityをユニットテストするための基底クラスです。が、このクラスを直接サブクラス化してテストを書くことはめったにないでしょう。代わりに、ActivityUnitTestCaseを使いましょう。

Activityを取り扱うための各種の部分をテスト用にエミュレートするフレームワークが含まれています。


ProviderTestCase2

ContentProviderをテストするための基底クラスです。内部でContextContentResolverをモックしており、本番のデータベースとは隔離された場所にテスト用データベースファイルを生成しています。このため、テストを実行したことが本番のアプリに影響を及ぼすことはありません。一方で、ファイル自体は生成され、データベースの操作も実際に行われるので、信頼性の高いテストを記述することが出来ます。


ServiceTestCase

Serviceをテストするための基底クラスです。Serviceのライフサイクルをテストするにあたって必要な依存の注入をするためのフックポイントが用意されています。


ApplicationTestCase

Applicationをテストするための基底クラスです。


LoaderTestCase

Loaderをテストするための基底クラスです。Loader自体は Support Package Library のメンバですが、このテストはそのライブラリに含まれていませんので、注意が必要です。

通常、Loaderの果たすべき仕事は非同期に実行されますが、テストでは同期実行されるようになります。


SyncBaseInstrumentation

ContentProviderの同期についてテストするための基底クラスです。テストの要求に応じて同期が実行されるかどうかをチェックしますので、同期中に実行される内部のロジックは別途ユニットテストで確認することになります。


モックフレームワーク

Perlのような言語では、そもそもモックするためのフレームワークが無くとも、適当にオブジェクトの振る舞いを書き換えてしまうことが出来るのですが、Java ではMockitoとかを使わない限りそのような芸当は困難を極めるので、別途モックを生成するためのものが用意されています。

と言っても、Mockitoほど強力なものではなく、単純に、Android のコンポーネントクラスを継承して、すべてのメソッドで例外を投げるようなものがほとんどです。


MockContext

Contextのモックです。すべてのメソッドが、UnsupportedOperationExceptionを投げます。

テストでは必要に応じてメソッドをオーバライドして、そのメソッドが呼ばれた、とか、呼ばれた時の引数チェック、とか、返す値を適当に書き換える、とかの目的で使用します。


IsolatedContext

本番と隔離する目的で使用されるContextです。本番のContextを使用するのではなく、テスト用のものを使用することで、本番に影響を与えないようにしています。

主には、ServiceBroadcastReceiverの管理や、Uriのパーミッションチェックなどがモックされています。


RenamingDelegatingContext

ファイルへの書き込みをモックするContextです。ProviderTestCase2でも使用され、実際にデータベースが作成される時のファイル名を書き換えています。

より一般的には、通常のファイル入出力をテスト用のものに差し替える機能を持っています。

が、SharedPreferencesについては差し替えをしてくれませんので、別途自分でモックする必要があります。


MockContentResolver/MockContentProvider

ContentResolverContentProviderのモックで、通常は両方をセットにして使います。

ContentProviderの挙動をモックすることで、テスト用に期待する振る舞いをするようスタブ化するために使います。ContentProviderのモックを作った上で、そのモックのContentProviderへアクセスさせるためにMockContentResolverを設定し、テスト対象のメソッドに引き渡します。


その他 Mock クラス

MockApplicationMockCursorMockPackageManagerMockDialogInterfaceMockResourcesがあります。

いずれも、UnsupportedOperationExceptionをスローするだけのクラスです。


Context への依存を設計で管理しよう

何かと怖がられがちなContextですが、しっかりと設計をしておくことで適切な管理を行えば、滅多なことでは事故ることは無いと思います。


UI のロジックは Helper に切り出す

それこそ、ほぼ手続き的なことしかしないのであれば、ステートレスにロジックを記述しておくことがよいでしょう。

ロジックそのものはステートレスに見える形で記述しますが、その実Viewはそれそのものが状態を持ち管理しているので、Viewの状態チェックとそれに合わせたロジックを書くことにはなろうかと思います。

よく、Activityprivateなメソッドが大量に連なることが有りますが、要するにそれらはヘルパメソッドでしかないので、Contextと一緒に必要なデータをメソッドに渡すよう設計しておけば、いくらでも UI のロジックを別のクラスに委譲することができます。

このようにしておくことで、AndroidTestCaseを使うことで UI のロジックのユニットテストを記述できるようになります。


Service のテストはややこしい

特に、IntentServiceは、その処理の本体は別のスレッドで実行されてしまうので、スレッドの待ち合わせを行う点で非常に面倒なことが多いです。

出来る限りIntentServiceそのものにはロジックを持たせず、使用したいモデルやヘルパのメソッドの呼び出しを行うだけにしておきたいです。そうすれば、少なくともモデルやヘルパのテストが成されれば、その呼び出しを、規約にしたがって行うだけで済むため、テストコストが最小限になります。


Context のモックをするクラスを活用する

ファイル入出力があるのならRenamingDelegatingContextを使うなど。それ以外にも、自分でモックをするためのコードを書くこともできるので、そういったものをフレームワーク化することで、ほとんどのロジックはユニットテスト可能です。

必要があるなら、SharedPreferencesFragmentManagerLoaderManagerActionBar等のモックも作っておくとよいでしょう(実はAmalgamにも入っています)。


機能テストを書きたい


Android フレームワークのユーティリティ

TouchUtilsを用いることで、タッチ操作をテストからエミュレートすることが出来ます。

ViewAssertsを用いることで、Viewの状態を手軽にチェックしアサートすることが出来ます。

また、基本的にテスト対象のActivityに対する依存の注入ができるフックポイントが用意されています。これによって、モデルレイヤを差し替えて、表示のロジックが適切に動作するかをチェックできるようになります。


Android フレームワークに足りないものをテストする

例えば、Toastに正しく文字列をセットして表示されたかどうかをテストする手段はありません。テストを実行することで、画面上にもToastが表示されますが、それらを目視確認するよりは、コードで確認出来たほうが楽ですよね。

そういった、かゆいところに手が届かない感じをイイカンジに吸収するものがRobotiumです。公式には、Android 向けの Selenium という位置づけで作られているようです。

Robotiumによって、テストから UI を簡単に操作できるようになり、Android フレームワークだけではテストできなかったこともテストできるようになります。

ただし、都度 UI の出力を待ち合わせるため、完全に動作させようとすると、実行時間がかかります。

ユニットテストでなんとかなる部分は、出来る限りAndroidTestCaseで頑張りたいものです。


まとめ的な

ActivityにしてもServiceにしてもBroadcastReceiverにしても、これらはコントローラであると言うことを前提に、モデルやデータ構造をうまく設計し実装することが、テストをしやすくしてくれますし、コントローラの見通しも良くなります。