悩めるテスト
たくさんあるAndroid SDKの素晴らしい点の一つは、機器側で動作させることが出来るテストの仕組みが最初から用意されている所だ。
ADTを使うとAndroidアプリケーションに対してのユニットテスト-プロジェクトを簡単に作成できるが、ウィザードの言うとおりにすると以下のように被テストプロジェクトに対して並行になる形でテストプロジェクトが作成される。
-+- org.kazz (被テスト側) | +-- src | +-- org | +-- kazz -+- org.kazz.test (テスト側) | +-- src | +-- org | +-- kazz | +-- test
アプリケーションとテストのコードは普通は分離したいものなので、これで良い。
Androidの場合は被テストコードとテストコードは互いに機器側にデプロイした状態でテストを実行する必要があるため、Instrumentationという他のプロセス-パッケージのコンテキスト上でテストクラスを動かすメカニズムを使うが、この便利なはずな仕組みが不便なことが多いのだ。(私だけかもしれない)
Activityに依存した何らかのテストを行う場合に、テスト時に生成するActivityはアプリケーション側のパッケージ(この場合はorg.kazz.apk内)に存在しており、Instrumentationの機能によりoeg.kazz.apk内のクラスがロードされてテストを実施できる。これなら想定通りだ。
では、テスト専用のActivityを作りたいが、それはテストアプリケーション側には置きたくない場合にどうするか?
すぐに考えつくのはtest側のプロジェクトにテスト専用のActivityを置く方法だ。
-+- org.kazz.test (テスト側) | +-- src | +-- org | +-- kazz | +-- test | +-- TestActivity.java
しかし、これは上手くいかない。
Instrumentationの仕組みはマニフェストで指定する必要があり、この例であれば、
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.kazz.test" android:versionCode="1" android:versionName="1.0"> <activity android:name=".test.TestActivity"/> : : <instrumentation android:targetPackage="org.kazz" android:name="android.test.InstrumentationTestRunner" /> </manifest>
と、targetPackage属性によりinstrumentationの対象パッケージを明示的に指定する必要がある。※従ってinstrumentation自身のパッケージである(この場合は)"org.kazz.test"内のActivityはロードできないのだ。
無理矢理ロードしようとすると、以下の例外が発生する。
java.lang.RuntimeException: Intent in process org.kazz resolved to different process org.kazz.test: Intent { act=android.intent.action.MAIN flg=0x10000000 cmp=org.kazz.test/org.kazz.TestActivity } :
という訳で、例えデプロイするアプリケーションに含めたくなくとも、被テスト側であるが故にアプリケーション側のパッケージにActivityを置く必要がある。
仕方が無いので、以下のようにディレクトリツリーだけは分けている
-+- org.kazz (被テスト側) | +-- src | +-- org | +-- kazz | +-- アクティビティやアプリケーションクラス、 | +-- test | +-- TestActivity.java
が、しかしこれだとデプロイ対象になってしまうので、根本的な解決にはならない。
※この「パッケージ」という言葉が曲者。これはAndroidアプリケーションのデプロイの単位であり.apk(android package)の名前として使用される。Javaのパッケージを指しておらず、ファイルシステムの階層構造とも一致しているとは限らない。「似ているが意味の違う言葉」ってのがやっかいなのだ。