【Android】UI Automator 2.0でUIテストをする
UI AutomatorはGoogleが開発しているAndroid向け自動UIテストフレームワークです。 2015年3月にバージョンが2.0になりGradleに対応しました。 有名なテストフレームワークのAppiumも中ではUI Automatorを実行しています。
Espressoとの違い
GoogleはテストフレームワークとしてEspressoも開発しています。EspressoとUI Automatorの違いとして最も大きいのは、UI Automatorは複数のアプリをテストできるということです。Espressoはアプリのソースコードに紐付いており、アプリのプロジェクトの中に入れる必要があります。それに対してUI Automatorは自分のアプリはもちろんのこと、設定アプリや自分で作ったツールアプリや他社のアプリ、通知バーなど自由に操作することができます。ソースコードと紐付いていないので、別プロジェクトで管理することもできますし、アプリと同じプロジェクトに含めることもできます。またUI Automatorは2.0からEspressoと併用できるようになったため、自分のアプリはEspresso、他のアプリを操作するときはUI Automatorといった書き方が可能です。
UI AutomatorはAndroid 4.3以上のみをサポートしています。まだまだAndroidでは最新のOSのみを対象とはできない状況なので、通常はAndroid 4.3以上のみを対象とていないことがほとんどかと思います。ここはProductFlavorを分けるかプロジェクトごと分けることで対応します。なおEspressoはAndroid 2.2からサポートしており、設定アプリなどを使わないテストのみであれば、Espressoのほうが幅広くテストを行えます。
設定
UI Automatorは2.0からAndroid Support Libraryの一部となり、build.gradleで簡単に使うことができるようになりました。 まずAndroid SDK ManagerからAndroid Support Repositoryをインストールします。
apply plugin: 'com.android.application' android { compileSdkVersion 25 buildToolsVersion "26.0.0" defaultConfig { applicationId "com.phicdy.uiautomator2sample" minSdkVersion 14 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } productFlavors { production { minSdkVersion 14 } uiTest { minSdkVersion 18 } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support:design:25.3.1' uiTestCompile 'com.android.support.test:runner:0.5' uiTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2' }
testInstrumentationRunnerをandroid.support.test.runner.AndroidJUnitRunnerに指定します。これでJUnit4形式でテストが書けるようになります。productFlavorをアプリ用とUI Automator用で分け、UI Automator用のminSdkVersionを18(Android 4.3)に設定します。今回はuiTestとしました。
最後に実行に必要なライブラリであるcom.android.support.test:runner:0.5とcom.android.support.test.uiautomator:uiautomator-v18:2.1.2をuiTestCompileで読み込みます。
テストを書く時や実行時は、Android Studio上のBuild VariantsをuiTestDebugに変更します。
これでUI Automatorの設定は終わりです。
テストを書く
テストはsrc/androidTest以下にJUnit4の書き方で追加していきます。 デフォルトでApplicationTestが入っていますが必要ないので消し、新たにテストを追加します。
package com.phicdy.uiautomator2sample; import android.content.Context; import android.content.Intent; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SdkSuppress; import android.support.test.runner.AndroidJUnit4; import android.support.test.uiautomator.By; import android.support.test.uiautomator.UiDevice; import android.support.test.uiautomator.UiObject2; import android.support.test.uiautomator.Until; import org.junit.Test; import org.junit.runner.RunWith; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.fail; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; @RunWith(AndroidJUnit4.class) @SdkSuppress(minSdkVersion = 18) public class MainUiTest { @Test public void floatingButtonTest() { UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); // Launch MainActivity Context context = InstrumentationRegistry.getContext(); Intent intent = context.getPackageManager().getLaunchIntentForPackage("com.phicdy.uiautomator2sample"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); // Click floating button UiObject2 btn = device.wait(Until.findObject(By.res("com.phicdy.uiautomator2sample:id/fab")), 3000); if (btn == null) fail("Floating button was not found"); btn.click(); UiObject2 snakeBar = device.wait(Until.findObject(By.res("com.phicdy.uiautomator2sample:id/snackbar_text")), 3000); assertNotNull(snakeBar); assertThat(snakeBar.getText(), is("Replace with your own action")); } }
今回はプロジェクト作成するときにTabbed Activityで作成したデフォルトのアプリのテストを作ります。 テストステップは以下の通りです。
- MainActiivtyを起動
- FloatingButtonを押す
- SnakeBarが出るので文言が"Replace with your own action"であることを確認
テストの初めにUiDeviceのインスタンスを取得します。 UiDeviceは端末の操作をしたり、UiObject2を取得したり、様々な場面で使います。 UiDeviceを管理するクラスを用意してもいいと思います。
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
MainActivity起動部分です。UI Automator 2.0ではContextが使えるようになったのでstartActivity()で起動します。 MainActivityはアプリ起動時に起動されるActivityなので、context.getPackageManager().getLaunchIntentForPackage()で対象のパッケージ名を指定して起動しています。
// Launch MainActivity Context context = InstrumentationRegistry.getContext(); Intent intent = context.getPackageManager().getLaunchIntentForPackage("com.phicdy.uiautomator2sample"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent);
FloatingButtonをクリックしてSnackBarのUiObject2を取得する部分です。
UiObject2 btn = device.wait(Until.findObject(By.res("com.phicdy.uiautomator2sample:id/fab")), 3000);
UI Automator 2.0では各ViewをUiObject2として取得します。主に取得には、UiDevice#findObject()かUiDevice#wait()を使います。 UiDevice#wait()を使うとViewが描画されるまで待ってくれるのでテストの成功率が上がります。
UiDevice#wait()にはSearchConditionとtimeoutを指定します。 SearchConditionはどの条件で待つかを指定します。Untilという便利なクラスがあるので、これを使ってSearchConditionを作ります。今回は特定のUiObject2が出るまで待つので、Unitl.findObject()を使います。Unitl.findObject()にはBySelectorを指定します。BySelectorはByクラスから生成してUiObject2を特定する条件を指定します。条件はresoruse IDやテキスト、クラス名などで指定します。resource IDが確実なので、アプリ側できるだけresource IDを設定します。Android SDKのtoolsにuiautomatorviewerというツールがあるのでこれで簡単にIDやViewの階層構造を確認できます。Android Studio 2.2からはこういった機能がAndroid Studio自体に追加されるようです。
UiObject2を取得したらnullチェックをしてエラーハンドリングをした後、クリックします。
if (btn == null) fail("Floating button was not found"); btn.click();
クリックするとSnackBarが出るので、テキスト部分を同様に取得します。 最後にテキスト部分のnullチェックと文言を確認します。
UiObject2 snakeBarText = device.wait(Until.findObject(By.res("com.phicdy.uiautomator2sample:id/snackbar_text")), 3000); assertNotNull(snakeBarText); assertThat(snakeBarText.getText(), is("Replace with your own action"));
最後に
UI Automator 2.0を使うことで、UIテストが書けました。 今回は1つのアプリだけを対象としたテストでしたが、例えば設定アプリを起動して機内モードをオンにし、エラーダイアログが出るか確認するといったテストもUI Autoamtor 2.0では可能です。 UIテストを書くことでCIでのリグレッションテストや手動テストの削減ができるので、少しずつ書いていきたいです。
今回のサンプルプロジェクトはこちらです。 以上です。