Android フレームワークは、あまり強力とはいえないものの、ある程度のテストフレームワークを内包しています。
ベースが JUnit3 なので、記法が古かったりすることもありますが、とりあえず JUnit3 の作法を身に付ければある程度のテストは書くことが出来ます。
Android のテストケースフレームワーク
Android がもつ各種のコンポーネントをテストするためのフレームワークが有ります。
いろいろな種類がありますが、おそらく最も頻繁に使うのはAndroidTestCase
でしょう。
AndroidTestCase
Android でユニットテストをするなら、ほぼ間違いなく使うであろうクラスです。
そのようなテストケースでは、すべてこのクラスを基底に作っていきます。
もう少し具体化して言うと、Context
に依存するすべてのテストはこのテストクラスを使うことで、イイカンジにテストができるようになります。つまり、テスト用に使うContext
をこのAndroidTestCase
がよしなに管理してくれるわけです。
InstrumentationTestCase
Android フレームワークにおけるもう一つの基底テストクラスです。
こちらは機能テストのためのもので、ボタンを押す、とか、テキストを入力する、と言ったことをテストするために使います。
しかし、何かとクセの強いフレームワークで、かつ何かと出来ないことも多いため、このクラスを純粋に使うことはめったにないでしょう。今や、Robotium というもう少しリッチに機能テストが書けるライブラリもあることですし…
通常、このクラスのサブクラスであるActivityInstrumentationTestCase2
を用いて機能テストを作ります。
ActivityTestCase
Activity
をユニットテストするための基底クラスです。が、このクラスを直接サブクラス化してテストを書くことはめったにないでしょう。代わりに、ActivityUnitTestCase
を使いましょう。
Activity
を取り扱うための各種の部分をテスト用にエミュレートするフレームワークが含まれています。
ProviderTestCase2
ContentProvider
をテストするための基底クラスです。内部でContext
とContentResolver
をモックしており、本番のデータベースとは隔離された場所にテスト用データベースファイルを生成しています。このため、テストを実行したことが本番のアプリに影響を及ぼすことはありません。一方で、ファイル自体は生成され、データベースの操作も実際に行われるので、信頼性の高いテストを記述することが出来ます。
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
を使用するのではなく、テスト用のものを使用することで、本番に影響を与えないようにしています。
主には、Service
やBroadcastReceiver
の管理や、Uri
のパーミッションチェックなどがモックされています。
RenamingDelegatingContext
ファイルへの書き込みをモックするContext
です。ProviderTestCase2
でも使用され、実際にデータベースが作成される時のファイル名を書き換えています。
より一般的には、通常のファイル入出力をテスト用のものに差し替える機能を持っています。
が、SharedPreferences
については差し替えをしてくれませんので、別途自分でモックする必要があります。
MockContentResolver/MockContentProvider
ContentResolver
とContentProvider
のモックで、通常は両方をセットにして使います。
ContentProvider
の挙動をモックすることで、テスト用に期待する振る舞いをするようスタブ化するために使います。ContentProvider
のモックを作った上で、そのモックのContentProvider
へアクセスさせるためにMockContentResolver
を設定し、テスト対象のメソッドに引き渡します。
その他 Mock クラス
MockApplication
、MockCursor
、MockPackageManager
、MockDialogInterface
、MockResources
があります。
いずれも、UnsupportedOperationException
をスローするだけのクラスです。
Context への依存を設計で管理しよう
何かと怖がられがちなContext
ですが、しっかりと設計をしておくことで適切な管理を行えば、滅多なことでは事故ることは無いと思います。
UI のロジックは Helper に切り出す
それこそ、ほぼ手続き的なことしかしないのであれば、ステートレスにロジックを記述しておくことがよいでしょう。
ロジックそのものはステートレスに見える形で記述しますが、その実View
はそれそのものが状態を持ち管理しているので、View
の状態チェックとそれに合わせたロジックを書くことにはなろうかと思います。
よく、Activity
にprivate
なメソッドが大量に連なることが有りますが、要するにそれらはヘルパメソッドでしかないので、Context
と一緒に必要なデータをメソッドに渡すよう設計しておけば、いくらでも UI のロジックを別のクラスに委譲することができます。
このようにしておくことで、AndroidTestCase
を使うことで UI のロジックのユニットテストを記述できるようになります。
Service のテストはややこしい
特に、IntentService
は、その処理の本体は別のスレッドで実行されてしまうので、スレッドの待ち合わせを行う点で非常に面倒なことが多いです。
出来る限りIntentService
そのものにはロジックを持たせず、使用したいモデルやヘルパのメソッドの呼び出しを行うだけにしておきたいです。そうすれば、少なくともモデルやヘルパのテストが成されれば、その呼び出しを、規約にしたがって行うだけで済むため、テストコストが最小限になります。
Context のモックをするクラスを活用する
ファイル入出力があるのならRenamingDelegatingContext
を使うなど。それ以外にも、自分でモックをするためのコードを書くこともできるので、そういったものをフレームワーク化することで、ほとんどのロジックはユニットテスト可能です。
必要があるなら、SharedPreferences
やFragmentManager
、LoaderManager
、ActionBar
等のモックも作っておくとよいでしょう(実はAmalgamにも入っています)。
機能テストを書きたい
Android フレームワークのユーティリティ
TouchUtils
を用いることで、タッチ操作をテストからエミュレートすることが出来ます。
ViewAsserts
を用いることで、View
の状態を手軽にチェックしアサートすることが出来ます。
また、基本的にテスト対象のActivity
に対する依存の注入ができるフックポイントが用意されています。これによって、モデルレイヤを差し替えて、表示のロジックが適切に動作するかをチェックできるようになります。
Android フレームワークに足りないものをテストする
例えば、Toast
に正しく文字列をセットして表示されたかどうかをテストする手段はありません。テストを実行することで、画面上にもToast
が表示されますが、それらを目視確認するよりは、コードで確認出来たほうが楽ですよね。
そういった、かゆいところに手が届かない感じをイイカンジに吸収するものがRobotium
です。公式には、Android 向けの Selenium という位置づけで作られているようです。
Robotium
によって、テストから UI を簡単に操作できるようになり、Android フレームワークだけではテストできなかったこともテストできるようになります。
ただし、都度 UI の出力を待ち合わせるため、完全に動作させようとすると、実行時間がかかります。
ユニットテストでなんとかなる部分は、出来る限りAndroidTestCase
で頑張りたいものです。
まとめ的な
Activity
にしてもService
にしてもBroadcastReceiver
にしても、これらはコントローラであると言うことを前提に、モデルやデータ構造をうまく設計し実装することが、テストをしやすくしてくれますし、コントローラの見通しも良くなります。