複数のアプリの UI をテストする

ユーザー インターフェース(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 ツールを起動するには:

  1. 物理デバイスでターゲット アプリを起動します。
  2. デバイスを開発マシンに接続します。
  3. ターミナル ウィンドウを開いて、<android-sdk>/tools/ ディレクトリに移動します。
  4. 次のコマンドでツールを実行します。
    $ uiautomatorviewer

アプリの UI プロパティを表示するには:

  1. uiautomatorviewer インターフェースで、[Device Screenshot] ボタンをクリックします。
  2. 左側のパネルのスナップショットに、uiautomatorviewer ツールによって特定された UI コンポーネントが表示されます。プロパティは右下のパネルにリストされ、レイアウト階層は右上のパネルに表示されます。
  3. オプションとして、[Toggle NAF Nodes] ボタンをクリックして、UI Automator からアクセスできない UI コンポーネントを表示できます。こうしたコンポーネントについては、限られた情報しか参照できない可能性があります。

Android が提供する UI コンポーネントの一般的なタイプについては、ユーザー インターフェースをご覧ください。

アクティビティのユーザー補助機能を確認する

UI Automator テスト フレームワークは、Android のユーザー補助機能を実装したアプリのパフォーマンスを向上させます。View 型の UI 要素、または SDK の View のサブクラスを使用する場合、これらのクラスはすでにユーザー補助サポートを実装しているため、改めて実装する必要はありません。

ただし、一部のアプリでは、カスタム UI 要素を使用して、より豊かなユーザー エクスペリエンスを提供できます。そのような要素は、自動的にはユーザー補助サポートを提供しません。SDK 以外の View のサブクラスのインスタンスがアプリに含まれている場合は、次の手順を実施して、それらの要素にユーザー補助機能を追加してください。

  1. ExploreByTouchHelper を拡張する具象クラスを作成します。
  2. setAccessibilityDelegate() を呼び出して、新しいクラスのインスタンスを特定のカスタム UI 要素に関連付けます。

カスタムビュー要素にユーザー補助機能を追加するためのガイダンスについては、アクセス可能なカスタムビューの構築をご覧ください。Android のユーザー補助に関する一般的なおすすめの方法については、アプリのユーザー補助機能の強化をご覧ください。

UI Automator テストクラスを作成する

UI Automator テストクラスは、JUnit 4 テストクラスと同じ方法で作成する必要があります。JUnit 4 テストクラスの作成方法と、JUnit 4 アサーションおよびアノテーションの使用方法の詳細については、インストゥルメント化単体テストクラスを作成するをご覧ください。

テストクラス定義の先頭に @RunWith(AndroidJUnit4.class) アノテーションを追加します。また、AndroidX Test が提供する AndroidJUnitRunner クラスを、デフォルトのテストランナーとして指定する必要があります。この手順の詳細については、デバイスまたはエミュレータで UI Automator テストを実行するをご覧ください。

UI Automator テストクラスに、次のプログラミング モデルを実装します。

  1. getInstance() メソッドを呼び出し、引数として Instrumentation オブジェクトを渡すことにより、テストしたいデバイスにアクセスするための UiDevice オブジェクトを取得します。
  2. findObject() メソッドを呼び出すことにより、UiObject オブジェクトを取得して、デバイスに表示される UI コンポーネント(フォアグラウンドの現在のビューなど)にアクセスします。
  3. UiObject メソッドを呼び出すことにより、その UI コンポーネントで実行する特定のユーザー インタラクションをシミュレートします。たとえば、performMultiPointerGesture() を呼び出して複数のタッチから成るジェスチャーをシミュレートし、setText() を呼び出してテキストフィールドを編集します。必要に応じてステップ 2 と 3 の API を繰り返し呼び出すことにより、複数の UI コンポーネントまたは連続するユーザー アクションを含む複雑なユーザー インタラクションをテストできます。
  4. ユーザー インタラクションが実行された後、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 インスタンスを再利用できます。UI Automator テスト フレームワークは、テストが UiObject インスタンスを使用して UI 要素をクリックするかプロパティをクエリするたびに、その時点で一致する表示を検索することにご注意ください。

次のスニペットは、アプリの [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();
    

結果を検証する

InstrumentationTestCaseTestCase を拡張したものであり、標準の 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 の基本的なサンプル。

コードラボ