AndroidのAsyncTaskをテストしてみるよ


Androidのテストコード書くのが日課のしかじろうだよ。
AsyncTaskのテストコードの書き方が分からなくて悩んでたけど、解決策が見つかったのでログに残します。意見・指摘とかあればコメントお願いしますね。

AsyncTaskとは

AsyncTaskはAndroidで使う非同期処理のクラスです。重たい処理(例えばHTTPでのDLや画像の変換処理など)をバックグラウンドで実行させるのに向いてます。処理の進行状況をプログレスバーに表示させることもできます。
似た処理にServiceがありますが、Serviceはバックグラウンドで処理を繰り返させる(GPSを監視し続けて特定の条件で通知を出すなど)などが目的なので、Activityから独立しています。
Activityと密接な非同期処理がAsyncTask。独立した非同期処理がServiceって感じで使い分けてます。
Serviceは専用のTestCaseがあるからいいけど、AsyncTaskは無いのでさてどうしたものか。

ロジックのテスト

まずは、非同期とかどうでもいいので、メソッドのロジックのテストがしたいです。でもAsyncTaskの #doInBackground() は protected なので、以下のやり方では動きません。

public class HogeAsyncTaskTest extends AndroidTestCase {

	public void testDoInBackground(){
		HogeAsyncTask task = new HogeAsyncTask();
                //protectedなのでコンパイルエラー
		//int result = task.doInBackground(0);
	}
}

task.execute() しちゃうと非同期処理が走っちゃう。HogeAsyncTask#doInBackground() をpublicにすればいいのですが、テストのためにコードを変えるのはその実装どうよって感じですし、テストコードを同一パッケージ収めるのもどうよ。
んで、色々悩んだ挙句、テスト用にHogeAsyncTask を継承したクラスを作って、そのクラスをテストすることにしました。

public class HogeAsyncTaskTest extends AndroidTestCase {

	public void testDoInBackground(){
		HogeAsyncTaskExt task = new HogeAsyncTaskExt();
		int result = task.doInBackground(0);
		assertEquals(1, result);
	}
	//テスト対象と実質同じクラス
        //だけど、doInBackgroundがpublicになっている。
	class HogeAsyncTaskExt extends HogeAsyncTask{
		@Override
		public Integer doInBackground(Integer... params) {
			return super.doInBackground(params);
		}
	}
}

これで、doInBackgroundのテストができました。やった。やったよ。

ここまでは、AsyncTaskのテスト方法というより、protectedメソッドのテストのやり方ですね。次は非同期処理のテスト方法です。

非同期のテスト

非同期処理のテストなので、 AsyncTask が終わったのを検知してその結果を調べたいです。でも、AsyncTaskは終わったことを教えてくれません。そこで、CountDownLatch と上記で使った HogeAsyncTask を拡張した HogeAsyncTaskExt を利用してテストします。

public class HogeAsyncTaskTest extends AndroidTestCase {
	//カウントダウンを管理するクラス。待機中に1回カウントされると処理が再開される。
	CountDownLatch signal;
	@Override
	protected void setUp() throws Exception {
		super.setUp();
		signal = new CountDownLatch(1);
	}

	public void testExecute() throws InterruptedException{
		HogeAsyncTaskExt task = new HogeAsyncTaskExt();
		task.execute(0);
                //カウントダウンが終わるまで処理が止まる。
		signal.await();
                //ここにassertとか書いてね
	}
	
	class HogeAsyncTaskExt extends HogeAsyncTask{
		@Override
                //doInBackground()の後に呼ばれる処理。
		protected void onPostExecute(Integer result) {
			super.onPostExecute(result);
                        //カウントダウンして、テスト処理を再開させる。
			signal.countDown();
		}
	}
}

カウントを1にセットしてテスト処理を実行して待機。executeの処理が終わったら(onPostExecuteが呼ばれたら)待機状態を解除してassertとかで結果を判定する。
こんな感じのコードでテストすることができました。やったねしかちゃん!