AndroidでJUnit

前回の記事で作成したプロジェクトのテストケースを作成
そして、テストでハマったことを中心に記事ってみました

するテスト

  • Buttonを押してイメージを表示
  • イメージをDragAndDrop
  • 画面遷移して、遷移したActivityをfinish(これが一番大変だった)

JUnitProjectの作成

  • Project→New Project → Android Test Projectを選択
  • Test Target
    • Browseボタン→An existing Android projectを選択

※ここではDragAndDropSample

    • projectを設定すると自動的にTestProjectNameが設定される
  • Finish
  • 空のTestProjectが生成される

■テストプロジェクトの確認

TestProject右クリック→BuildPath→ダイアログ表示→Projectsタブにテスト対象のプロジェクトが追加されている


JUnitTestCaseの作成

  • 右クリック→New→JUnitTestCase
  • New JUnit Test Caseの設定
    • Name: DragAndDropSampleTest
    • Superclass: ActivityInstrumentationTestCase2
    • Class under test: Browseボタンをクリック→DragAndDropSample.java
  • Nextボタン
  • Test Methods選択画面 以下をチェック
    • onClickShowButton
    • onClickButon02
    • onTouch
  • テストケースが作成される
  • パッケージ名の変更

※デフォルトで作成されるパッケージはテスト元パッケージ名.testになっているが、testを削除する。

テストコード作成

■Tip

  • パッケージ名はターゲットプロジェクトと同じにする

※同一パッケージはprotectが普通に呼び出せる

  • デフォルトコンストラクタを作成
  • superを引数付きで呼び出す
    • 第1引数:パッケージ名
    • 第2引数:ターゲットクラスのクラス型
  • Activityを取得するのはgetActivity()
  • UIThreadでしか処理できないイベントはAcitivty#runOnUiThread内で実装
  • イベントを実行した後はgetInstrumentation().waitForIdleSync();を使ってメインスレッドが戻ってくるまで待機(説明に御幣があるが、多分looperが空になるまで待っていると思われる)
  • TouchUtilsを使うとDragAndDropのテストができる

※本当はTouchUtilsについてももっと触れたいのですが、また別の機会にでも・・・

テストを実行する

テストはメソッド単位で実行することができる

実行方法
Outlineビューでメソッドを右クリック→Debug As→Android JUnit Test

■testOnClickShowButton
Imageの表示テスト

■testOnClickButton02
画面遷移のテスト

	public void testOnClickButton02() {
		final DragAndDropSample activity = getActivity();
		final Button button = (Button) activity
		.findViewById(R.id.Button02);
		
		activity.runOnUiThread(new Runnable() {
			public void run() {
				button.performClick();
			}
		});
		getInstrumentation().waitForIdleSync();
	}

■testOnTouch()
TouchUtilを使ってドラッグアンドドロップのテスト
水平方向にx+100、y+100だけ動かす。
実際にテストを走らせると本当に勝手に動いているからおもしろい。

		//Drag and Drop
		TouchUtils.dragViewTo(DragAndDropSampleTest.this, target,
				Gravity.CENTER_HORIZONTAL, activity.currentX + 100,
				activity.currentY + 100);


※画像がくずれているのはキャプチャのタイミングのせい

【はまったこと】UnitTestが途中で止まる!!!

メソッド単位のテストは成功しても全体を通して実行するとテストが途中で止まる!!
という自体が発生します。

残念テスト・・・orz、次のテストケースが走りません!!

■原因
原因はいろいろありますが、一番多いパターンはAcitivityの遷移
画面が遷移してしまうとUnitテストのターゲット以外のAcitivtyがフォアグランドで動いてしまうのでテストは止まります。
一応、メソッド単位での成功はするのですが、次のテストケースを呼び出せなくなります。
今回のケースだとtestOnClickButton02は終了でも画面が変わったので次のテストケースが呼べない。

なんでこうなるの?
【推測】今Androidが実行しているのはTestアプリです。なのでテストアプリが一番上にいます。そのテストアプリがテスト上でActivityの起動をしました。

その結果、起動したActivityが一番上にくるという残念な状態になっている。(自信ないけどたぶん?知ってる人いたら教えて下さい)

解決策

ようするに一番上のActivityをテストケースからfinishすればいい。
以下のことをすればいける。

  • ActivityMonitorを使う!
    • 第1引数:監視したいActivityをパッケージ名付クラス名
    • 第2引数:よくわかんない
    • 第3引数:戻り値があるかないからし
  • getInstrumentation().addMonitor(monitor)で気になるActivityを監視対象に追加!
  • getInstrumentation().waitForMonitorWithTimeout(monitor, 2000); で起動したActivityを取得!
  • 取得したActivityをfinish

ソース
testOnClickButton02を以下のように修正

	public void testOnClickButton02() {
		ActivityMonitor monitor = new ActivityMonitor("com.dd.sample.Next", null, false);
		getInstrumentation().addMonitor(monitor);
		
		final DragAndDropSample activity = getActivity();
		final Button button = (Button) activity
		.findViewById(R.id.Button02);
		
		activity.runOnUiThread(new Runnable() {
			public void run() {
				button.performClick();
			}
		});

		Activity next = getInstrumentation().waitForMonitorWithTimeout(monitor, 2000); 
		assertEquals(monitor.getHits(),1);
		if(next != null){
			next.finish();
		}

	}


全ソース

package com.dd.sample;

import android.app.Activity;
import android.app.Instrumentation.ActivityMonitor;
import android.test.ActivityInstrumentationTestCase2;
import android.test.TouchUtils;
import android.view.Gravity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import com.dd.sample.DragAndDropSample;
import com.dd.sample.DragAndDropSampleTest;
import com.dd.sample.R;

public class DragAndDropSampleTest extends
		ActivityInstrumentationTestCase2<DragAndDropSample> {

	public DragAndDropSampleTest() {
		super("com.dd.sample", DragAndDropSample.class);
	}

	public void testOnClickShowButton() throws Exception {
		final DragAndDropSample activity = getActivity();
		final ImageView target = (ImageView) activity
				.findViewById(R.id.ImageView01);

		final Button button = (Button) activity
		.findViewById(R.id.Button01);
		
		//とりあえず消しとく
		activity.runOnUiThread(new Runnable() {
			public void run() {
				target.setVisibility(View.GONE);
			}
		});
		getInstrumentation().waitForIdleSync();
		Thread.sleep(1000);

		//Viewを表示
		activity.runOnUiThread(new Runnable() {
			public void run() {
				button.performClick();
			}
		});
		getInstrumentation().waitForIdleSync();
		
		//Viewが表示されているチェック
		assertEquals(target.getVisibility(), View.VISIBLE);

		activity.finish();
	}

	public void testOnClickButton02() {
		ActivityMonitor monitor = new ActivityMonitor("com.dd.sample.Next", null, true);
		getInstrumentation().addMonitor(monitor);
		
		final DragAndDropSample activity = getActivity();
		final Button button = (Button) activity
		.findViewById(R.id.Button02);
		
		activity.runOnUiThread(new Runnable() {
			public void run() {
				button.performClick();
			}
		});

		Activity next = getInstrumentation().waitForMonitorWithTimeout(monitor, 2000); 
		assertEquals(monitor.getHits(),1);
		if(next != null){
			next.finish();
		}

	}

	/**
	 * イメージをドラッグアンドドロップして消えたイメージを再表示する
	 */
	public void testOnTouch() {
		final DragAndDropSample activity = getActivity();
		final ImageView target = (ImageView) activity
				.findViewById(R.id.ImageView01);

		//Drag and Drop
		TouchUtils.dragViewTo(DragAndDropSampleTest.this, target,
				Gravity.CENTER_HORIZONTAL, activity.currentX + 100,
				activity.currentY + 100);
		

		//Viewが消えてる
		assertEquals(target.getVisibility(), View.GONE);
		final Button button = (Button) activity
		.findViewById(R.id.Button01);
		
		try {
			Thread.sleep(1500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}


		//Viewを表示
		activity.runOnUiThread(new Runnable() {
			public void run() {
				button.performClick();
			}
		});
		
		//Buttonクリック待ち
		getInstrumentation().waitForIdleSync();

		//Viewが表示された
		assertEquals(target.getVisibility(), View.VISIBLE);
		
		try {
			Thread.sleep(1500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		activity.finish();
	}

}