複数のアプリにまたがるユーザー インタラクションを対象とするユーザー インターフェース(UI)テストでは、ユーザーフローに他のアプリやシステム UI が関与する場合にアプリが正しく動作することを検証できます。このようなユーザーフローの例として、メッセージング アプリがあります。この種のアプリは、ユーザーがテキスト メッセージを入力できるようにした後、Android の連絡先選択ツールを起動して、ユーザーがメッセージの送信先を選択できるようにします。その後、元のアプリに制御を戻して、ユーザーがメッセージを送信できるようにします。
このレッスンでは、AndroidX Test が提供する UI Automator テスト フレームワークを使用して、このような UI テストを作成する方法を説明します。UI Automator API を使用すると、どの Activity
がフォーカスされているかにかかわらず、デバイス上の表示要素を操作できます。テストでは、UI コンポーネントやそのコンテンツ説明に表示されるテキストなど、使い勝手のよい記述子を使用して UI コンポーネントを検索できます。UI Automator テストは、Android 4.3(API レベル 18)以上を実行しているデバイスで実施できます。
UI Automator テスト フレームワークは、インストゥルメンテーション ベースの API であり、AndroidJUnitRunner
テストランナーと連携して機能します。
UI Automator API リファレンスもお読みください。また、UI Automator コードサンプルをお試しください。
UI Automator をセットアップする
UI Automator を使用して UI テストを作成する前に、AndroidX Test 用にプロジェクトをセットアップするの説明に従って、テスト ソースコードの場所とプロジェクトの依存関係を構成してください。
Android アプリ モジュールの build.gradle
ファイルで、UI Automator ライブラリへの依存関係の参照を設定する必要があります。
dependencies { ... androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' }
UI Automator テストを最適化するには、最初にターゲット アプリの UI コンポーネントを検査して、それらがアクセス可能であることを確認する必要があります。最適化のヒントについては、次の 2 つのセクションで説明します。
デバイス上の UI を検査する
テストを設計する前に、デバイスに表示される UI コンポーネントを検査します。UI Automator テストでそのようなコンポーネントにアクセスできるようにするには、表示テキストラベルと android:contentDescription
値の両方またはいずれかがコンポーネントに存在することを確認します。
uiautomatorviewer
ツールは、レイアウト階層を検査し、デバイスのフォアグラウンドに表示される UI コンポーネントのプロパティを確認するのに便利な視覚的インターフェースを提供します。この情報により、UI Automator できめ細かいテストを作成することが可能になります。たとえば、特定の表示プロパティに対応する UI セレクタを作成できます。
uiautomatorviewer
ツールを起動するには:
- 物理デバイスでターゲット アプリを起動します。
- デバイスを開発マシンに接続します。
- ターミナル ウィンドウを開いて、
<android-sdk>/tools/
ディレクトリに移動します。 - 次のコマンドでツールを実行します。
$ uiautomatorviewer
アプリの UI プロパティを表示するには:
uiautomatorviewer
インターフェースで、[Device Screenshot] ボタンをクリックします。- 左側のパネルのスナップショットにカーソルを合わせると、
uiautomatorviewer
ツールによって特定された UI コンポーネントが表示されます。プロパティは右下のパネルにリストされ、レイアウト階層は右上のパネルに表示されます。 - オプションとして、[Toggle NAF Nodes] ボタンをクリックして、UI Automator からアクセスできない UI コンポーネントを表示できます。こうしたコンポーネントについては、限られた情報しか参照できない可能性があります。
Android が提供する UI コンポーネントの一般的なタイプについては、ユーザー インターフェースをご覧ください。
アクティビティのユーザー補助機能を確認する
UI Automator テスト フレームワークは、Android のユーザー補助機能を実装したアプリのパフォーマンスを向上させます。View
型の UI 要素か、SDK の View
のサブクラスを使用する場合、これらのクラスはすでにユーザー補助サポートを実装しているため、改めて実装する必要はありません。
ただし、一部のアプリでは、カスタム UI 要素を使用して、より豊かなユーザー エクスペリエンスを提供できます。そのような要素は、自動的にはユーザー補助サポートを提供しません。SDK 以外の View
のサブクラスのインスタンスがアプリに含まれている場合は、次の手順を実施して、それらの要素にユーザー補助機能を追加してください。
ExploreByTouchHelper
を拡張する具象クラスを作成します。setAccessibilityDelegate()
を呼び出して、新しいクラスのインスタンスを特定のカスタム UI 要素に関連付けます。
カスタムビュー要素にユーザー補助機能を追加するためのガイダンスについては、カスタムビューのユーザー補助機能を強化するをご覧ください。Android のユーザー補助に関する一般的なおすすめの方法については、アプリのユーザー補助機能を強化するをご覧ください。
UI Automator テストクラスを作成する
UI Automator テストクラスは、JUnit 4 テストクラスと同じ方法で作成する必要があります。JUnit 4 テストクラスの作成方法と、JUnit 4 アサーションおよびアノテーションの使用方法の詳細については、インストゥルメント化単体テストクラスを作成するをご覧ください。
テストクラス定義の先頭に @RunWith(AndroidJUnit4.class)
アノテーションを追加します。また、AndroidX Test で提供される AndroidJUnitRunner
クラスをデフォルトのテストランナーとして指定する必要があります。この手順の詳細については、デバイスまたはエミュレータで UI Automator テストを実行するをご覧ください。
UI Automator テストクラスに、次のプログラミング モデルを実装します。
getInstance()
メソッドを呼び出し、引数としてInstrumentation
オブジェクトを渡すことにより、テスト対象のデバイスにアクセスするためのUiDevice
オブジェクトを取得します。findObject()
メソッドの呼び出しにより、デバイスに表示される UI コンポーネント(フォアグラウンドの現在のビューなど)にアクセスするためのUiObject
オブジェクトを取得します。UiObject
メソッドの呼び出しにより、該当の UI コンポーネントで実行する特定のユーザー インタラクションをシミュレートします。たとえば、performMultiPointerGesture()
を呼び出して複数のタップから成る操作をシミュレートし、setText()
を呼び出してテキスト フィールドを編集します。必要に応じてステップ 2 とステップ 3 の API を繰り返し呼び出すことにより、複数の UI コンポーネントまたは一連のユーザー アクションを含む複雑なユーザー インタラクションをテストできます。- ユーザー インタラクションが実行された後、UI に予期される状態または動作が反映されていることを確認します。
上記の手順については、以降のセクションで詳しく説明します。
UI コンポーネントにアクセスする
UiDevice
オブジェクトは、デバイスの状態にアクセスして操作するための主要な手段です。テストでは、UiDevice
メソッドを呼び出して、現在のデバイスの向きや表示サイズなど、さまざまなプロパティの状態をチェックできます。
また、UiDevice
オブジェクトを使用して、デバイスレベルのアクション(デバイスに特定の回転をさせる、ハードウェアの D-pad ボタンを押す、ホームボタンやメニューボタンを押すなど)を実行できます。
デバイスのホーム画面からテストを始めることをおすすめします。ホーム画面(またはデバイスで選択したその他のスタート地点)から、UI Automator API が提供するメソッドを呼び出して、特定の UI 要素を選択および操作できます。
次のコード スニペットは、テストで UiDevice
のインスタンスを取得して、ホームボタンの押下をシミュレートする方法を示しています。
Kotlin
import org.junit.Before import androidx.test.runner.AndroidJUnit4 import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.By import androidx.test.uiautomator.Until ... private const val BASIC_SAMPLE_PACKAGE = "com.example.android.testing.uiautomator.BasicSample" private const val LAUNCH_TIMEOUT = 5000L private const val STRING_TO_BE_TYPED = "UiAutomator" @RunWith(AndroidJUnit4::class) @SdkSuppress(minSdkVersion = 18) class ChangeTextBehaviorTest2 { private lateinit var device: UiDevice @Before fun startMainActivityFromHomeScreen() { // Initialize UiDevice instance device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) // Start from the home screen device.pressHome() // Wait for launcher val launcherPackage: String = device.launcherPackageName assertThat(launcherPackage, notNullValue()) device.wait( Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT ) // Launch the app val context = ApplicationProvider.getApplicationContext<Context>() val intent = context.packageManager.getLaunchIntentForPackage( BASIC_SAMPLE_PACKAGE).apply { // Clear out any previous instances addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) } context.startActivity(intent) // Wait for the app to appear device.wait( Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)), LAUNCH_TIMEOUT ) } }
Java
import org.junit.Before; import androidx.test.runner.AndroidJUnit4; import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.By; import androidx.test.uiautomator.Until; ... @RunWith(AndroidJUnit4.class) @SdkSuppress(minSdkVersion = 18) public class ChangeTextBehaviorTest { private static final String BASIC_SAMPLE_PACKAGE = "com.example.android.testing.uiautomator.BasicSample"; private static final int LAUNCH_TIMEOUT = 5000; private static final String STRING_TO_BE_TYPED = "UiAutomator"; private UiDevice device; @Before public void startMainActivityFromHomeScreen() { // Initialize UiDevice instance device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); // Start from the home screen device.pressHome(); // Wait for launcher final String launcherPackage = device.getLauncherPackageName(); assertThat(launcherPackage, notNullValue()); device.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT); // Launch the app Context context = ApplicationProvider.getApplicationContext(); final Intent intent = context.getPackageManager() .getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE); // Clear out any previous instances intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); context.startActivity(intent); // Wait for the app to appear device.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)), LAUNCH_TIMEOUT); } }
この例では、@SdkSuppress(minSdkVersion = 18)
ステートメントにより、UI Automator フレームワークの要件に従って、Android 4.3(API レベル 18)以上のデバイスでのみテストが実行されるようにしています。
findObject()
メソッドを使用して、特定のセレクタ条件に一致するビューを表す UiObject
を取得します。必要に応じて、アプリテストの他の部分で作成した UiObject
インスタンスを再利用できます。テストで UiObject
インスタンスを使用して UI 要素をクリックするかプロパティをクエリするたびに、UI Automator テスト フレームワークはその時点で一致する表示を検索することにご注意ください。
次のスニペットは、アプリの [Cancel] ボタンと [OK] ボタンを表す UiObject
インスタンスをテストで構築する方法を示しています。
Kotlin
val cancelButton: UiObject = device.findObject( UiSelector().text("Cancel").className("android.widget.Button") ) val okButton: UiObject = device.findObject( UiSelector().text("OK").className("android.widget.Button") ) // Simulate a user-click on the OK button, if found. if (okButton.exists() && okButton.isEnabled) { okButton.click() }
Java
UiObject cancelButton = device.findObject(new UiSelector() .text("Cancel") .className("android.widget.Button")); UiObject okButton = device.findObject(new UiSelector() .text("OK") .className("android.widget.Button")); // Simulate a user-click on the OK button, if found. if(okButton.exists() && okButton.isEnabled()) { okButton.click(); }
セレクタを指定する
アプリの特定の UI コンポーネントにアクセスするには、UiSelector
クラスを使用します。このクラスは、現在表示されている UI の特定の要素に対するクエリを表します。
一致する要素が複数見つかった場合、レイアウト階層内で最初に一致した要素がターゲットの UiObject
として返されます。UiSelector
を構築する際は、複数のプロパティを連結して検索を絞り込むことができます。一致する UI 要素が見つからない場合は、UiAutomatorObjectNotFoundException
がスローされます。
childSelector()
メソッドを使用すると、複数の UiSelector
インスタンスをネストできます。たとえば、次のコード例は、現在表示されている UI で最初の ListView
を検索した後、その ListView
内でテキスト プロパティ「Apps」を持つ UI 要素を検索する方法を示しています。
Kotlin
val appItem: UiObject = device.findObject( UiSelector().className("android.widget.ListView") .instance(0) .childSelector( UiSelector().text("Apps") ) )
Java
UiObject appItem = device.findObject(new UiSelector() .className("android.widget.ListView") .instance(0) .childSelector(new UiSelector() .text("Apps")));
セレクタを指定するときは、テキスト要素やコンテンツ記述子ではなく、リソース ID(ただし、UI 要素に割り当てられている場合)を使用するのがおすすめの方法です。要素の中にはテキスト要素を持たないものもあります(たとえば、ツールバーのアイコンなど)。テキストによるセレクタ指定には確実性がなく、UI に少しでも変更があるとテストが失敗する可能性があります。また、異なる言語に拡張できず、テキスト セレクタが翻訳された文字列と一致しないこともあります。
セレクタの条件としてオブジェクトの状態を指定する方法が適切な場合もあります。たとえば、すべてのチェック済み要素のリストを選択してチェックを解除するには、引数を true
に設定して checked()
メソッドを呼び出します。
アクションを実行する
テストで UiObject
オブジェクトを取得したら、UiObject
クラスのメソッドを呼び出して、そのオブジェクトが表す UI コンポーネントでユーザー インタラクションを実行できます。次のようなアクションの指定が可能です。
-
click()
: UI 要素の表示境界の中心をクリックします。 -
dragTo()
: このオブジェクトを任意の座標にドラッグします。 -
setText()
: 編集可能フィールドの内容をクリアした後、フィールドにテキストを設定します。一方、clearTextField()
メソッドは、編集可能フィールドの既存のテキストをクリアします。 -
swipeUp()
:UiObject
上で、上にスワイプするアクションを実行します。同様に、swipeDown()
、swipeLeft()
、swipeRight()
の各メソッドは、それぞれ対応するアクションを実行します。
UI Automator テスト フレームワークでは、getContext()
で Context
オブジェクトを取得することにより、シェルコマンドを使用せずに Intent
を送信したり Activity
を起動したりすることができます。
次のスニペットは、テストで Intent
を使用してテスト対象のアプリを起動する方法を示しています。このアプローチは、電卓アプリのテストのみが重要で、アプリを起動するランチャーは無視してよい場合に役立ちます。
Kotlin
fun setUp() { ... // Launch a simple calculator app val context = getInstrumentation().context val intent = context.packageManager.getLaunchIntentForPackage(CALC_PACKAGE).apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) } // Clear out any previous instances context.startActivity(intent) device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT) }
Java
public void setUp() { ... // Launch a simple calculator app Context context = getInstrumentation().getContext(); Intent intent = context.getPackageManager() .getLaunchIntentForPackage(CALC_PACKAGE); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // Clear out any previous instances context.startActivity(intent); device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT); }
コレクションでアクションを実行する
アイテムのコレクション(音楽アルバムの収録曲や受信トレイ内のメールのリストなど)に対するユーザー インタラクションをシミュレートするには、UiCollection
クラスを使用します。UiCollection
オブジェクトを作成するには、UI コンテナ、もしくは子 UI 要素のラッパー(子 UI 要素を含むレイアウト ビューなど)を検索する UiSelector
を指定します。
次のコード スニペットは、FrameLayout
内に表示される動画アルバムを表す UiCollection
をテストで構築する方法を示しています。
Kotlin
val videos = UiCollection(UiSelector().className("android.widget.FrameLayout")) // Retrieve the number of videos in this collection: val count = videos.getChildCount( UiSelector().className("android.widget.LinearLayout") ) // Find a specific video and simulate a user-click on it val video: UiObject = videos.getChildByText( UiSelector().className("android.widget.LinearLayout"), "Cute Baby Laughing" ) video.click() // Simulate selecting a checkbox that is associated with the video val checkBox: UiObject = video.getChild( UiSelector().className("android.widget.Checkbox") ) if (!checkBox.isSelected) checkBox.click()
Java
UiCollection videos = new UiCollection(new UiSelector() .className("android.widget.FrameLayout")); // Retrieve the number of videos in this collection: int count = videos.getChildCount(new UiSelector() .className("android.widget.LinearLayout")); // Find a specific video and simulate a user-click on it UiObject video = videos.getChildByText(new UiSelector() .className("android.widget.LinearLayout"), "Cute Baby Laughing"); video.click(); // Simulate selecting a checkbox that is associated with the video UiObject checkBox = video.getChild(new UiSelector() .className("android.widget.Checkbox")); if(!checkBox.isSelected()) checkbox.click();
スクロール可能なビューでアクションを実行する
ディスプレイ上の垂直または水平スクロールをシミュレートするには、UiScrollable
クラスを使用します。この手法は、UI 要素が画面外に配置されていて、表示するにはスクロールが必要な場合に役立ちます。
次のコード スニペットは、[設定] メニューを下にスクロールして [About tablet] オプションをクリックする操作をシミュレートする方法を示しています。
Kotlin
val settingsItem = UiScrollable(UiSelector().className("android.widget.ListView")) val about: UiObject = settingsItem.getChildByText( UiSelector().className("android.widget.LinearLayout"), "About tablet" ) about.click()
Java
UiScrollable settingsItem = new UiScrollable(new UiSelector() .className("android.widget.ListView")); UiObject about = settingsItem.getChildByText(new UiSelector() .className("android.widget.LinearLayout"), "About tablet"); about.click();
結果を検証する
InstrumentationTestCase
は TestCase
の拡張クラスであり、標準の JUnit Assert
メソッドを使用して、アプリ内の UI コンポーネントが期待される結果を返すかどうかをテストできます。
次のスニペットは、テストで電卓アプリのボタンの位置を特定して順にクリックし、正しい結果が表示されることを確認する方法を示しています。
Kotlin
private const val CALC_PACKAGE = "com.myexample.calc" fun testTwoPlusThreeEqualsFive() { // Enter an equation: 2 + 3 = ? device.findObject(UiSelector().packageName(CALC_PACKAGE).resourceId("two")).click() device.findObject(UiSelector().packageName(CALC_PACKAGE).resourceId("plus")).click() device.findObject(UiSelector().packageName(CALC_PACKAGE).resourceId("three")).click() device.findObject(UiSelector().packageName(CALC_PACKAGE).resourceId("equals")).click() // Verify the result = 5 val result: UiObject2 = device.findObject(By.res(CALC_PACKAGE, "result")) assertEquals("5", result.text) }
Java
private static final String CALC_PACKAGE = "com.myexample.calc"; public void testTwoPlusThreeEqualsFive() { // Enter an equation: 2 + 3 = ? device.findObject(new UiSelector() .packageName(CALC_PACKAGE).resourceId("two")).click(); device.findObject(new UiSelector() .packageName(CALC_PACKAGE).resourceId("plus")).click(); device.findObject(new UiSelector() .packageName(CALC_PACKAGE).resourceId("three")).click(); device.findObject(new UiSelector() .packageName(CALC_PACKAGE).resourceId("equals")).click(); // Verify the result = 5 UiObject result = device.findObject(By.res(CALC_PACKAGE, "result")); assertEquals("5", result.getText()); }
デバイスまたはエミュレータで UI Automator テストを実行する
UI Automator テストは、Android Studio またはコマンドラインから実行できます。プロジェクトで、デフォルトのインストゥルメンテーション ランナーとして AndroidJUnitRunner
を指定してください。
参考情報
Android テストで UI Automator を使用する方法の詳細については、次のリソースをご覧ください。
サンプル
- BasicSample: UI Automator の基本的なサンプルです。