目次
テストディレクトリの作成
ユニットテスト
テストの設定
テストの実行
IDE
コマンド
テストレポート
description: この章では、ユニットテストとシナリオテストについて解説します。 —
参考:Testing Fundamentals | Android Developers
参考:Activity Testing | Android Developers
参考:Service Testing | Android Developers
参考:Content Provider Testing | Android Developers
参考:UI Testing | Android Developers
参考:What To Test | Android Developers
参考:uiautomator | Android Developers
目次
テストプロジェクトの作成
Android では、アプリ本体のプロジェクトと、テストケース用のプロジェクトが分離されています。
テストプロジェクトのセットアップは、Eclipse 上から実行することができます。
File > New から、Project… または Other… を選択してください(Ctrl + N
または Command + N
)。
新規プロジェクトの画面から、Android > Android Test Project を選択します。
次に、テストプロジェクトのセットアップをします。
テストプロジェクトの名前と、配置場所を指定します。
次に、テストのターゲットとするアプリ本体のプロジェクトを選択します。
最後に、テストプロジェクトが利用する SDK のバージョンを選択して完了です。
このテストプロジェクトを用いて、ユニットテストや、シナリオテストを記述していきます。
ユニットテスト
Android では、フレームワーク自身がユニットテストのフレームワークを持っています。
このフレームワークは、JUnit3 をベースにしています。
テスト対象に応じて、独自に拡張されたテストケースクラスが用意されています。
AndroidTestCase
一般的な、Android のテストケースクラスです。
Context
、特にActivity
のContext
を必要とするクラスのテストをするために作られています。
これによって、リソースへのアクセスも可能となっています。
テストケースは、メソッドごとに定義します。
この時、メソッド名の命名規則として、先頭に必ずtest
と記述する必要があります(JUnit3 の仕様)。
また、テストケースは、記述した順に実行されるとは限りません。
テストケースを実行するごとに、前準備と後始末をするためのメソッドも用意されています。
それぞれ、事前準備をするメソッドはAndroidTestCase#setUp()
、後始末をするメソッドはAndroidTestCase#tearDown()
です。
特に、テスト内でファイル I/O やデータベース接続などのリソースを扱う場合は、後始末をするtearDown()
で、必ずリソースのクローズを行うようにします。
以下に、テストのターゲットとするクラスのサンプルと、そのターゲットをテストするサンプルを示します。
public class SampleTestTarget1 {
public int add(int left, int right) {
return left + right;
}
public int div(int left, int right) {
if (right == 0) throw new IllegalArgumentException("right operand must not be zero.");
return left / right;
}
}
public class MyTestCase extends AndroidTestCase {
// テストの前準備のメソッド。テストケースの実行ごとに呼ばれる。
// 事前準備中に何らかの例外が起こる可能性があるので、例外をスローする宣言をする。
@Override
protected void setUp() throws Exception {
super.setUp();
}
// テストの後始末のメソッド。テストケースの実行ごとに呼ばれる。
// 後始末中に何らかの例外が起こる可能性があるので、例外をスローする宣言をする。
@Override
protected void tearDown() throws Exception {
super.tearDown();
}
// テストケース本体。名前は必ず test から始まる
// テスト中に異常が発生した場合(=例外がスローされた場合)、テストを Fail とするためその例外をそのまま投げるようにする
public void testAdd() throws Exception {
SampleTestTarget1 target = new SampleTestTarget1();
// 第 1 引数 に期待値、第 2 引数に実際の計算を入れて、等しいかどうか比較する
assertEquals(2, target.add(1, 1));
assertEquals(3, target.add(1, 2));
assertEquals(4, target.add(2, 2));
}
public void testDivide() throws Exception {
SampleTestTarget1 target = new SampleTestTarget1();
assertEquals(2, target.div(4, 2));
try {
target.div(1, 0);
// 期待した例外が来ない場合、強制的に Fail する
fail("no argument checking!?");
} catch (IllegalArgumentException e) {
// 例外をテストする場合。
// テストしたい例外のみをキャッチして、それ以外は throws 宣言で Fail 扱いとする
// テストしたい例外をキャッチした上で、何もしなければ Pass 扱いとなる
}
}
}
テストで利用する、検証用のメソッドは以下のようなものがあります。
引数を 2 つとるメソッドは、第 1 引数が期待する値、第 2 引数が実際にテスト対象を呼び出した結果を渡すようにします。
メソッド | 用途 |
---|---|
assertEquals() |
第 1 引数と第 2 引数が等しいかどうか |
assertNull() |
引数のオブジェクトがnull かどうか |
assertNotNull() |
引数のオブジェクトがnull でないかどうか |
assertTrue() |
引数のboolean がtrue かどうか |
assertFalse() |
引数のboolean がfalse かどうか |
assertSame() |
第 1 引数と第 2 引数が、同じオブジェクトを参照しているかどうか |
assertNotSame() |
第 1 引数と第 2 引数が、同じオブジェクトを参照していないかどうか |
これらは、JUnit3 由来の検証メソッドですが、これら以外にも検証の手段が提供されています。
MoreAsserts | AndroidDevelopers
ViewAsserts | AndroidDevelopers
ProviderTestCase
ContentProvider
のためのテストケースです。
テスト用に、モックのContext
を利用してテストを行います。
テストを実行すると、テスト用に本体とは分離されたContext
のもと、データベース等もすべてテスト向けに特別なものが用意されます。
このため、本体で利用するデータベースへの影響はありません。
ContentProvider
のテストでは、query
やinsert
、update
、delete
、getType
、onCreate
の動作を保証しておくことが必要です。また、不正なパラメータが与えられた場合などの異常系のテストも必要です。
加えて、ContentProvider
を外部向けにエクスポートする場合、アクセスに利用するUri
についての正常系と異常系のテストを含めておくことが求められます。
// ProviderTestCase ではなく ProviderTestCase2 を継承すること
public class SampleContentProviderTestCase extends ProviderTestCase2<TestTargetContentProvider> {
private Context mMockContext;
public SampleContentProviderTestCase() {
this(TestTargetContentProvider.class, TestTargetContentProvider.AUTHORITY);
}
public SampleContentProviderTestCase(Class<TestTargetContentProvider> providerClass, String providerAuthority) {
super(providerClass, providerAuthority);
}
@Override
protected void setUp() throws Exception {
super.setUp();
mMockContext = getMockContext();
}
public void testInsertNewText() throws Exception {
// モックされたコンテキストから、ContentResolver を取り出し、それを経由してデータベースへとアクセスする
// テストからアクセスする場合、毎回データベースが作りなおされるため、後始末としてテストで利用したデータを消すなどは必要ない
// また、データベースそのものも、テスト用のものが作成されるため、本体のデータベースには影響を及ぼさないようになっている
ContentResolver resolver = mMockContext.getContentResolver();
ContentValues values = new ContentValues();
values.put("name", "KeithYokoma");
Uri newUri = resolver.insert(TestTargetContentProvider.CONTENT_URI, values);
assertNotNull(newUri);
assertEquals(ContentUris.withAppendedId(TestTargetContentProvider.CONTENT_URI, 1), newUri);
}
}
ServiceTestCase
Service
のためのテストケースです。
Service
が期待通りのライフサイクルで動作することを保証するためのテストを記述します。
public class SampleServiceTestCase extends ServiceTestCase<TestTargetService> {
public SampleServiceTestCase() {
this(TestTargetService.class);
}
public SampleServiceTestCase(Class<TestTargetService> serviceClass) {
super(serviceClass);
}
public void testStartingProperly() throws Exception {
// サービスを問題なく開始できること
// 何かあれば例外が飛ぶこともチェック
startService(new Intent(getContext(), TestTargetService.class));
}
public void testBinding() throws Exception {
// 開始するサービスの場合で、バインドをサポートしない場合は、bindService の返り値が null となるので、それをチェック
IBinder binder = bindService(new Intent(getContext(), TestTargetService.class));
assertNull(binder);
}
}
MockContext
Context
をモックするための仕組みとして、MockContext
クラスが用意されています。
このクラス自身は、どのメソッドを読んでも、UnsupportedOperationException
を投げるだけになっており、自分でモックしたい部分や、モックすべき部分を、MockContext
を継承して記述し、各テストケースクラスで、そのContext
を注入して使います。
public class SampleTestTarget2 {
public void startSubActivity(Context context, String hogeExtra) {
Intent intent = new Intent(context, SubActivity.class);
intent.putExtra("hoge", hogeExtra);
context.startActivity(intent);
}
}
public class SampleTestTarget2TestCase extends AndroidTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
}
// startSubActivity() を呼び出すテスト
public void testStartSubActivity() throws Exception {
SampleTestTarget2 target = new SampleTestTarget2();
// AndroidTestCase が持っている ActivityContext ではなく、自分でモックした Context
target.startSubActivity(new SampleTestTarget2Context(getContext()), "hogehoge");
}
// Context#startActivity() が、期待通りのコンポーネントに Intent を投げているかテストするための
// MockContext
private static class SampleTestTarget2Context extends MockContext {
private Context mContext;
public SampleTestTarget2Context(Context baseContext) {
mContext = baseContext;
}
@Override
public String getPackageName() {
return mContext.getPackageName();
}
@Override
public void startActivity(Intent intent) {
// Intent から、Intent の送り先のコンポーネント情報を取り出して、期待値と一致するか確認する
ComponentName component = intent.getComponent();
assertEquals(SubActivity.class.getCanonicalName(), component.getClassName());
// Extra に期待するものが有るか確認する
assertTrue(intent.hasExtra("hoge"));
assertEquals("hogehoge", intent.getStringExtra("hoge"));
}
}
}
MockContentResolver と MockContentProvider
ContentProvider
のデータを元にしたモデルや、Activity
のテストを行う際、実際のデータを用いてしまうと、デバイスによってテストがうまくいく場合とうまくいかない場合が出来てしまいます。
これを回避するため、ContentProvider
への問い合わせの際に、直接ContentProvider
を使用せず、テスト用に定義したモックのデータを返すようにするものが、MockContentResolver
とMockContentProvider
です。
ContentResolver
は、ContentProvider
へのアクセスの窓口となるものですので、テスト対象のクラスで、自分で定義したMockContentProvider
へアクセスするように設定します。
query()
メソッドによるCursor
オブジェクトの取得をモックする際には、MatrixCursor
クラスを使用します。
MatrixCursor
クラスによって、仮想のカラムとデータをCursor
オブジェクトに詰め込むことが出来るようになります。
以下に、テスト対象のコードと、それに対応するテストコードを示します。
public class SampleTestTarget3 {
// DB に問い合わせた結果を、適宜データ構造に当てはめてオブジェクトを作って、リストで返すメソッド
public List<SampleDBEntity> getAllListFromDB(Context context) {
List<SampleDBEntity> list = new ArrayList<SampleDBEntity>();
ContentResolver resolver = context.getContentResolver();
Cursor c = null;
try {
c = resolver.query(TestTargetContentProvider.CONTENT_URI, new String[] { BaseColumns._ID, "name" }, null, null, null);
// 結果があれば、ポインタを先頭に移動してから処理を始める
if (c.moveToFirst()) {
do {
// Cursor からデータを取り出してインスタンスを作ってリストに詰める、1 回以上の繰り返し
int id = c.getInt(c.getColumnIndex(BaseColumns._ID));
String name = c.getString(c.getColumnIndex("name"));
list.add(new SampleDBEntity(id, name));
} while (c.moveToNext());
}
} finally {
// リソースを閉じる
if (c != null) {
c.close();
}
}
return list;
}
}
public class SampleTestTarget3TestCase extends AndroidTestCase {
public void testGetAllList() throws Exception {
SampleTestTarget3 target = new SampleTestTarget3();
List<SampleDBEntity> list = target.getAllListFromDB(new SampleMockContext(getContext()));
assertNotNull(list);
MoreAsserts.assertNotEmpty(list);
assertEquals(3, list.size());
{
SampleDBEntity entity = list.get(0);
assertEquals(1, entity.getId());
assertEquals("KeithYokoma", entity.getName());
}
{
SampleDBEntity entity = list.get(1);
assertEquals(2, entity.getId());
assertEquals("HogeFugao", entity.getName());
}
{
SampleDBEntity entity = list.get(2);
assertEquals(3, entity.getId());
assertEquals("HiyoHiyo", entity.getName());
}
}
// モックの本体。ContentProvider そのものをモックしてしまう。
// モックしたいメソッドを適宜オーバライドすること。
private static class SampleMockContentProvider extends MockContentProvider {
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// クエリ結果のモック作成
// MatrixCursor を用いてデータをモックする
MatrixCursor cursor = new MatrixCursor(projection);
cursor.addRow(new String[] {"1", "KeithYokoma"});
cursor.addRow(new String[] {"2", "HogeFugao"});
cursor.addRow(new String[] {"3", "HiyoHiyo"});
return cursor;
}
@Override
public void attachInfo(Context context, ProviderInfo info) {
}
}
private static class SampleMockContentResolver extends MockContentResolver {
public SampleMockContentResolver(Context context) {
// モックの ContentProvider へアクセスしに行くよう設定する
ContentProvider provider = new SampleMockContentProvider();
// AndroidManifest に記述する ContentProvider の宣言をここで動的に行う
ProviderInfo info = new ProviderInfo();
info.authority = TestTargetContentProvider.AUTHORITY;
info.enabled = true;
info.packageName = TestTargetContentProvider.class.getPackage().getName();
provider.attachInfo(context, info);
// ContentProvider を追加する
addProvider(TestTargetContentProvider.AUTHORITY, provider);
}
}
private static class SampleMockContext extends MockContext {
private Context mContext;
public SampleMockContext(Context context) {
mContext = context;
}
// コンテキストをモックして、独自の MockContentResolver を返すようにしてしまう
@Override
public ContentResolver getContentResolver() {
return new SampleMockContentResolver(mContext);
}
}
}
このようにすることで、データベースのデータそのものが分離されるので、テストケースでは、テスト対象のビジネスロジックをテストできるようになります。
テストの実行
実行したいテストクラスを選択して、テストを実行します。
右クリックメニューから、Run As > Android JUnit Test を選択すると、自動でターゲットプロジェクトとテストプロジェクトをそれぞれビルドし、ターゲットプロジェクトの apk とテストプロジェクトの apk をインストールし、テストが動き始めます。
テストが動き始めると、順次テスト結果が表示されます。
すべて成功すると、緑色のインディケータが表示されます。
なにか 1 つでも失敗したケースがあると、その時点でインディケータの色が紺色になります。
また、実行したテストケースの一覧も表示されます。
この一覧のなかで、成功・失敗の表示がありますので、どのテストケースが成功・失敗したかが分かるようになっています。
シナリオテスト
ユニットテストでは、各クラス(特にモデルのクラス)が適切な振る舞いをするかどうかをテストしました。
シナリオテストでは、ボタンを押したり、画面を回転した時に、アプリがどのように振る舞うかをテストします。
これを機能テストとも呼んだりしています。
ActivityInstrumentationTestCase
Activity に対して、テストプロジェクトから様々な操作を行うためのテストケースクラスです。
実行すると、実際に実機やエミュレータ上で画面が立ち上がり、自動で UI の操作が行われます。
UI の操作は、立ち上げたActivity
のインスタンスを通して、レイアウトのリソースからView
オブジェクトを取り出して操作をするものと、キーイベントのように Instrumentation を通して操作するものがあります。
View のタッチイベントを再現するためのライブラリとして、TouchUtils が用意されています。
// ActivityInstrumentationTestCase2 を継承して、機能テストを書く
public class SampleActivityInstrumentationTestCase extends
ActivityInstrumentationTestCase2<MainActivity> {
public SampleActivityInstrumentationTestCase() {
this(MainActivity.class);
}
public SampleActivityInstrumentationTestCase(Class<MainActivity> activityClass) {
super(activityClass);
}
public void testCountUpScenario() throws Exception {
// getActivity() の呼び出しで、テスト対象の Activity が立ち上がる
Activity activity = getActivity();
// UI 操作による View の状態を見るために、View のインスタンスを取り出す
TextView counter = (TextView) activity.findViewById(R.id.ClickCounter);
Button button = (Button) activity.findViewById(R.id.CountEventTrigger);
// 最初は 0
assertEquals("0", counter.getText().toString());
// ボタンのクリックをシミュレート
TouchUtils.clickView(this, button);
// クリックしたら、カウンタの値がインクリメントされる
assertEquals("1", counter.getText().toString());
TouchUtils.clickView(this, button);
// もう一度クリック
assertEquals("2", counter.getText().toString());
// 画面回転する
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
Thread.sleep(1000L);
// クリック回数が引き継がれているはず
assertEquals("2", counter.getText().toString());
}
public void testCallSubActivityAndReturn() throws Exception {
Activity activity = getActivity();
// UI 操作による View の状態を見るために、View のインスタンスを取り出す
TextView counter = (TextView) activity.findViewById(R.id.ClickCounter);
Button button = (Button) activity.findViewById(R.id.CountEventTrigger);
Button button2 = (Button) activity.findViewById(R.id.CallSubActivity);
// 最初は 0
assertEquals("0", counter.getText().toString());
// ボタンのクリックをシミュレート
TouchUtils.clickView(this, button);
// クリックしたら、カウンタの値がインクリメントされる
assertEquals("1", counter.getText().toString());
// Activity の起動を監視する(厳密には Intent を監視する)オブジェクトを作る
ActivityMonitor monitor = new ActivityMonitor(SubActivity.class.getCanonicalName(), null, true);
// 監視オブジェクトを登録
getInstrumentation().addMonitor(monitor);
// Launch SubActivity をクリック
TouchUtils.clickView(this, button2);
// 起動を待つ
Activity newActivity = getInstrumentation().waitForMonitorWithTimeout(monitor, 3000L);
// 1 つの Activity が起動しているはず
assertEquals(1, monitor.getHits());
// 終わる
if (newActivity != null)
newActivity.finish();
// 戻ってきても状態が復帰できるはず
assertEquals("1", counter.getText().toString());
}
}
Robotium
ActivityInstrumentationTestCase
だけでは足りない、より幅広い UI のテストをするためのフレームワークです。
ActivityInstrumentationTestCase
では、レイアウトの実装を知っている必要がありましたが、Robotium では、この部分もブラックボックス化することができるようになります。
Android ネイティブアプリ版の Selenium とも呼ばれています。
robotium - The world’s leading Android™ test automation framework …
Robotium は、jar 形式のライブラリとなっており、テストプロジェクトにlibs
ディレクトリを作成して、その中に Robotium の jar を格納することで使用可能となります。
Robotium では、レイアウトの実装をテストから分離するため、Solo
と呼ばれる窓口を介して UI を操作します。
本来は UI スレッド上で UI 操作をするための記述をする必要のある部分を、すべてSolo
オブジェクトが担ってくれることと、レイアウトの実装を知らなくても、ボタンを押したり、メニューを表示したり、長押ししたりと言ったインタラクションが簡単に記述できることから、ActivityInstrumentationTestCase2
よりもブラックボックスなテストが可能です。
また、複数のActivity
に渡るシナリオテストも、ActivityMonitor
を利用するより非常に簡単に記述できるようになっています。
// Solo クラスを import
import com.jayway.android.robotium.solo.Solo;
// 継承するものは、ActivityInstrumentationTestCase2
public class SampleRobotiumTestCase extends ActivityInstrumentationTestCase2<MainActivity> {
public SampleRobotiumTestCase() {
this(MainActivity.class);
}
public SampleRobotiumTestCase(Class<MainActivity> activityClass) {
super(activityClass);
}
public void testCountUpScenario() throws Exception {
Activity activity = getActivity();
// Robotium ライブラリのコアで、UI の操作の窓口となるオブジェクト
Solo solo = new Solo(getInstrumentation(), activity);
// MainActivity が立ち上がってフォアグラウンドにいる
solo.assertCurrentActivity("MainActivity now.", MainActivity.class);
// カウンタの数字が 0 であることを確認する
// 0 と書かれた TextView を画面上から探し出し、あればその TextView オブジェクトを返すメソッド
assertTrue(solo.searchText("0"));
// Count up と書かれたボタンをクリックする (View の id を知る必要はない)
solo.clickOnButton("Count up");
// 0 と書かれた TextView はなくなり、1 と書かれたTextView になるはず
assertFalse(solo.searchText("0"));
assertTrue(solo.searchText("1"));
solo.clickOnButton("Count up");
assertFalse(solo.searchText("1"));
assertTrue(solo.searchText("2"));
// 横画面に回転する
solo.setActivityOrientation(Solo.LANDSCAPE);
// 状態が保存され、2 と書かれた TextView が引き続き居るはず
assertTrue(solo.searchText("2"));
}
public void testCallSubActivityAndReturn() throws Exception {
Activity activity = getActivity();
Solo solo = new Solo(getInstrumentation(), activity);
// MainActivity が立ち上がってフォアグラウンドにいる
solo.assertCurrentActivity("MainActivity now.", MainActivity.class);
// カウンタの数字が 0 であることを確認する
// 0 と書かれた TextView を画面上から探し出し、あればその TextView オブジェクトを返すメソッド
assertTrue(solo.searchText("0"));
// Count up と書かれたボタンをクリックする (View の id を知る必要はない)
solo.clickOnButton("Count up");
// 0 と書かれた TextView はなくなり、1 と書かれたTextView になるはず
assertFalse(solo.searchText("0"));
assertTrue(solo.searchText("1"));
// Launch SubActivity ボタンを押す
solo.clickOnButton("Launch SubActivity");
// SubActivity が起動し、フォアグラウンドに居るはず
solo.assertCurrentActivity("SubActivity now.", SubActivity.class);
// メニューキーを押した
solo.sendKey(KeyEvent.KEYCODE_MENU);
// ActionBarを使っているなら、以下でも良い
// solo.clickOnActionBarItem(0);
// 戻る
solo.getCurrentActivity().finish();
// 状態復帰できているはず
assertTrue(solo.searchText("1"));
}
}
UIAutomator
ADT リビジョン 21 以降から導入され、Android 4.1 以降で動作するように作られた、UI のテストフレームワークです。
Android SDK に含まれています。
プロジェクトのセットアップ
UIAutomator を使ったプロジェクトの作成は、Android 用プロジェクトではなく、Java 用プロジェクトとして作成します。
次に、テストプロジェクトで利用するライブラリのセットアップを行います。
Libraries タブから、Add Libraries… を選択し、表示されたダイアログから、JUnit を選択します。
バージョンを聞かれるので、JUnit3 を選択します。
次に、Add External Jars… から、android.jar と uiautomator.jar をピックアップします。
この 2 つは、SDK のディレクトリ以下のplatforms/android-16/
またはplatforms/android-17
ディレクトリに配置されています。
選択したら、このような画面になるはずです。
次に、コマンドラインから、UIAutomator のプロジェクトディレクトリへ移動し、下記のコマンドを実行します。
$ android create uitest-project -p UIAutomatorSample -n UIAutomatorSample -t 21
Added file UIAutomatorSample/build.xml
以上で、プロジェクトのセットアップは終了です。
テストの作成
UiAutomatorTestCase
を継承して、テストケースクラスを作ります。
JUnit3 をベースとするため、テストケースのメソッド定義は他のテストケースと同様な命名規則に従います。
// 端末ホーム画面からアプリを起動して操作するテスト
public class MainActivityAutomatorTestCase extends UiAutomatorTestCase {
public void testMainActivityButtonClick() throws Exception {
// デバイスオブジェクトの取得。このオブジェクトを介して、デバイスの状態を取得したり、UI の操作を行ったりする。
UiDevice device = getUiDevice();
// ホームボタンを押す
device.pressHome();
// ホームボタンに有るターゲットのアイコンをタップする
UiObject launchIcon = new UiObject(new UiSelector().textContains("TestTarget"));
launchIcon.clickAndWaitForNewWindow();
// 起動した(指定したパッケージ名のアプリがフォアグラウンドに居て、オブジェクトの取得が無事に出来る)
UiObject app = new UiObject(new UiSelector().packageName("jp.mixi.sample.test"));
assertTrue(app.exists());
device.pressBack();
// もう一度タップ
UiObject launchIcon2 = new UiObject(new UiSelector().textContains("TestTarget"));
launchIcon2.clickAndWaitForNewWindow();
// カウンターの初期値は 0
UiObject firstCounterState = new UiObject(new UiSelector().text("0"));
assertTrue(firstCounterState.exists());
// カウントアップ
UiObject countUp = new UiObject(new UiSelector().text("Count up"));
countUp.click();
// カウンターが更新される
UiObject secondCounterState = new UiObject(new UiSelector().text("1"));
assertTrue(secondCounterState.exists());
}
}
テストの実行
テストプロジェクトの中で、下記のコマンドを実行します。
$ ant build
Buildfile: /Path/To/UIAutomatorTestProject/build.xml
-check-env:
[checkenv] Android SDK Tools Revision 21.1.0
[checkenv] Installed at /Path/To/android-sdk-macosx
-build-setup:
[echo] Resolving Build Target for UIAutomatorTestProject...
[getuitarget] Project Target: Android 4.2.2
[getuitarget] API level: 17
[echo] ----------
[echo] Creating output directories if needed...
[mkdir] Created dir: /Path/To/UIAutomatorTestProject/bin/classes
-pre-compile:
compile:
[javac] Compiling 1 source file to /Path/To/UIAutomatorTestProject/bin/classes
-post-compile:
-dex:
[dex] input: /Path/To/UIAutomatorTestProject/bin/classes
[dex] Converting compiled files and external libraries into /Path/To/UIAutomatorTestProject/bin/classes.dex...
-post-dex:
-jar:
[jar] Building jar: /Path/To/UIAutomatorTestProject/bin/UIAutomatorTestProject.jar
-post-jar:
build:
BUILD SUCCESSFUL
Total time: 2 seconds
コマンドを実行すると、プロジェクトディレクトリ以下のbin/
ディレクトリの中に、jar ファイルが生成されます。
次に、この jar ファイルをデバイスへ転送し、デバイス上で jar を実行します。
$ adb push UIAutomatorTestProject.jar /data/local/tmp
$ adb shell uiautomator runtest UIAutomatorTestProject.jar -c jp.mixi.sample.test.automator.MainActivityAutomatorTestCase
すると、画面がひとりでに動き始め、コンソールにテスト結果が出力されるようになります。
INSTRUMENTATION_STATUS: current=1
INSTRUMENTATION_STATUS: id=UiAutomatorTestRunner
INSTRUMENTATION_STATUS: class=jp.mixi.sample.test.automator.MainActivityAutomatorTestCase
INSTRUMENTATION_STATUS: stream=
jp.mixi.sample.test.automator.MainActivityAutomatorTestCase:
INSTRUMENTATION_STATUS: numtests=1
INSTRUMENTATION_STATUS: test=testMainActivityButtonClick
INSTRUMENTATION_STATUS_CODE: 1
INSTRUMENTATION_STATUS: current=1
INSTRUMENTATION_STATUS: id=UiAutomatorTestRunner
INSTRUMENTATION_STATUS: class=jp.mixi.sample.test.automator.MainActivityAutomatorTestCase
INSTRUMENTATION_STATUS: stream=.
INSTRUMENTATION_STATUS: numtests=1
INSTRUMENTATION_STATUS: test=testMainActivityButtonClick
INSTRUMENTATION_STATUS_CODE: 0
INSTRUMENTATION_STATUS: stream=
Test results for WatcherResultPrinter=.
Time: 5.77
OK (1 test)
INSTRUMENTATION_STATUS_CODE: -1
通常、ActivityInstrumentationTestCase
等では、アプリの署名が合致しないものはテストができませんでしたが、UIAutomator では、署名に関係なく UI の操作をテストすることができます。
これにより、例えば、他のアプリとの連携をする部分のテストも容易に記述できるようになります。
画面から UI の要素を取り出して操作するには、画面に表示されているレイアウトの階層構造をたどることの他、サンプルのように、表示されている文字列でたどったり、あるいは、View のクラス名 (FQDN) からたどったりすることで、要素を取り出して操作をすることが出来るようになっています。
ImageView など文字列でたどれないものは、android:contentDescription
属性を元に辿る手段もあります。
デバイス上で表示されている画面のレイアウト構造を見るための手段として、uiautomatorviewer
というコマンドも用意されています。