単一アプリの UI をテストする

単一アプリ内のユーザー インタラクションをテストして、ユーザーがアプリを操作する際に、予期せぬ結果に遭遇したり、ユーザー エクスペリエンスの問題が生じたりしないことを確認します。アプリの UI が正しく機能するか確認する必要がある場合には、常にユーザー インターフェース(UI)のテスト作成を心掛けるようにしてください。

AndroidX Test が提供する Espresso テスト フレームワークでは、UI テストを作成するための複数の API が用意されており、単一のターゲット アプリ内におけるユーザー インタラクションのシミュレーションが可能です。Espresso によるテストは、Android 2.3.3(API レベル 10)以上を実行しているデバイスで実施できます。Espresso を使用する主な利点は、テスト対象アプリの UI とテスト アクションが自動的に同期されることです。Espresso はメインスレッドがアイドル状態になったタイミングを検出するので、適切な時点でテストコマンドを実行できます。これにより、テストの信頼性が向上します。また、この機能により、Thread.sleep() などのタイミングの問題を解決する仕組みをテストコードに追加する必要がなくなります。

Espresso テスト フレームワークは、インストゥルメンテーション ベースの API であり、AndroidJUnitRunner テストランナーと連携して機能します。

Espresso をセットアップする

Espresso で UI テストを作成する前に、Espresso ライブラリへの依存関係の参照を設定してください。

    dependencies {
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
    }
    

テストデバイスでは、アニメーションをオフにします。テストデバイスでシステム アニメーションがオンのままになっていると、予期しない結果が発生するかテストが失敗する可能性があります。アニメーションをオフにするには、[設定] の [開発者向けオプション] を開いて、次のオプションをすべてオフにします。

  • ウィンドウ アニメスケール
  • トランジション アニメスケール
  • Animator 再生時間スケール

Espresso の機能で、コア API が提供するもの以外を使用できるようにプロジェクトをセットアップしたい場合は、Espresso のガイドをご覧ください。

Espresso テストクラスを作成する

Espresso テストを作成するには、次のプログラミング モデルに従います。

  1. onView() メソッドまたは AdapterView コントロールの onData() メソッドを呼び出して、Activity のテストを行う UI コンポーネント(アプリのログインボタンなど)を見つけます。
  2. ViewInteraction.perform() メソッドまたは DataInteraction.perform() メソッドを呼び出してユーザー アクション(ログインボタンのクリックなど)を渡すことにより、その UI コンポーネントで実行する特定のユーザー インタラクションをシミュレートします。同じ UI コンポーネントで複数のアクションを連続して行うには、メソッド引数にカンマ区切りリストを使用してアクションのチェーンを指定します。
  3. 必要に応じて上記の手順を繰り返し、ターゲット アプリの複数のアクティビティで構成されるユーザーフローをシミュレートします。
  4. ユーザー インタラクションが実行された後、ViewAssertions メソッドを使用して、UI に予期される状態または動作が反映されているかをチェックします。

上記の手順については、以降のセクションで詳しく説明します。

次のコード スニペットは、この基本的なワークフローのテストクラスでの起動方法を示しています。

Kotlin

    onView(withId(R.id.my_view))            // withId(R.id.my_view) is a ViewMatcher
            .perform(click())               // click() is a ViewAction
            .check(matches(isDisplayed()))  // matches(isDisplayed()) is a ViewAssertion
    

Java

    onView(withId(R.id.my_view))            // withId(R.id.my_view) is a ViewMatcher
            .perform(click())               // click() is a ViewAction
            .check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion
    

ActivityTestRule で Espresso を使用する

このセクションでは、JUnit 4 形式で新しい Espresso テストを作成する方法について説明します。また、ActivityTestRule を使用して、記述する必要があるボイラープレート コードの量を減らす方法についても示します。テスト フレームワークでは、ActivityTestRule を使用することで、@Test アノテーション付きの各テストメソッドと @Before アノテーション付きのすべてのメソッドが行われる前に、テスト対象のアクティビティが起動します。さらに、テストが終了して @After アノテーション付きのメソッドがすべて実行された後で、アクティビティのシャットダウンを処理します。

Kotlin

    package com.example.android.testing.espresso.BasicSample

    import org.junit.Before
    import org.junit.Rule
    import org.junit.Test
    import org.junit.runner.RunWith

    import androidx.test.rule.ActivityTestRule
    import androidx.test.runner.AndroidJUnit4

    @RunWith(AndroidJUnit4::class)
    @LargeTest
    class ChangeTextBehaviorTest {

        private lateinit var stringToBetyped: String

        @get:Rule
        var activityRule: ActivityTestRule<MainActivity>
                = ActivityTestRule(MainActivity::class.java)

        @Before
        fun initValidString() {
            // Specify a valid string.
            stringToBetyped = "Espresso"
        }

        @Test
        fun changeText_sameActivity() {
            // Type text and then press the button.
            onView(withId(R.id.editTextUserInput))
                    .perform(typeText(stringToBetyped), closeSoftKeyboard())
            onView(withId(R.id.changeTextBt)).perform(click())

            // Check that the text was changed.
            onView(withId(R.id.textToBeChanged))
                    .check(matches(withText(stringToBetyped)))
        }
    }
    

Java

    package com.example.android.testing.espresso.BasicSample;

    import org.junit.Before;
    import org.junit.Rule;
    import org.junit.Test;
    import org.junit.runner.RunWith;

    import androidx.test.rule.ActivityTestRule;
    import androidx.test.runner.AndroidJUnit4;

    @RunWith(AndroidJUnit4.class)
    @LargeTest
    public class ChangeTextBehaviorTest {

        private String stringToBetyped;

        @Rule
        public ActivityTestRule<MainActivity> activityRule
                = new ActivityTestRule<>(MainActivity.class);

        @Before
        public void initValidString() {
            // Specify a valid string.
            stringToBetyped = "Espresso";
        }

        @Test
        public void changeText_sameActivity() {
            // Type text and then press the button.
            onView(withId(R.id.editTextUserInput))
                    .perform(typeText(stringToBetyped), closeSoftKeyboard());
            onView(withId(R.id.changeTextBt)).perform(click());

            // Check that the text was changed.
            onView(withId(R.id.textToBeChanged))
                    .check(matches(withText(stringToBetyped)));
        }
    }
    

UI コンポーネントにアクセスする

Espresso でテスト対象のアプリを操作する前に、まず UI コンポーネントかビューを指定する必要があります。Espresso では、アプリのビューとアダプタを指定するために Hamcrest マッチャーを使用できます。

ビューを見つけるには、onView() メソッドを呼び出して、ターゲットとするビューを指定するビュー マッチャーを渡します。これについては、ビュー マッチャーを指定するで詳しく説明します。onView() メソッドは、テストによるビューの操作を可能にする ViewInteraction オブジェクトを返します。ただし、RecyclerView レイアウトでビューを検索する場合、onView() メソッドの呼び出しが機能しないことがあります。その場合は、AdapterView でビューを検索するに記載されている手順に従ってください。

: onView() メソッドは、指定されたビューが有効かどうかをチェックしません。代わりに、Espresso は、提供されているマッチャーを使用して現在のビュー階層のみを検索します。一致するものが見つからない場合、メソッドからは NoMatchingViewException がスローされます。

次のコード スニペットは、EditText フィールドにアクセスし、テキストの文字列を入力し、仮想キーボードを閉じてからボタンをクリックするテストの作成方法を示しています。

Kotlin

    fun testChangeText_sameActivity() {
        // Type text and then press the button.
        onView(withId(R.id.editTextUserInput))
                .perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard())
        onView(withId(R.id.changeTextButton)).perform(click())

        // Check that the text was changed.
        ...
    }
    

Java

    public void testChangeText_sameActivity() {
        // Type text and then press the button.
        onView(withId(R.id.editTextUserInput))
                .perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard());
        onView(withId(R.id.changeTextButton)).perform(click());

        // Check that the text was changed.
        ...
    }
    

ビュー マッチャーを指定する

次のアプローチにより、ビュー マッチャーを指定できます。

  • ViewMatchers クラスのメソッドを呼び出します。たとえば、ビューで表示されるテキスト文字列を探すことによりビューを見つけるには、次のようにメソッドを呼び出します。

    Kotlin

        onView(withText("Sign-in"))
        

    Java

        onView(withText("Sign-in"));
        

    同様に、次の例に示すように、withId() を呼び出してビューのリソース ID(R.id)を指定することもできます。

    Kotlin

        onView(withId(R.id.button_signin))
        

    Java

        onView(withId(R.id.button_signin));
        

    Android リソース ID が一意であるという保証はありません。テスト内で、複数のビューで使用されるリソース ID のマッチングを試みると、Espresso は AmbiguousViewMatcherException をスローします。

  • Hamcrest の Matchers クラスを使用します。allOf() メソッドを使用して、containsString()instanceOf() などの複数のマッチャーを組み合わせることが可能です。このアプローチにより、マッチングの結果をさらに絞り込むことができます。次の例をご覧ください。

    Kotlin

        onView(allOf(withId(R.id.button_signin), withText("Sign-in")))
        

    Java

        onView(allOf(withId(R.id.button_signin), withText("Sign-in")));
        

    次の例に示すように、not キーワードを使用して、マッチャーに一致しないビューをフィルタリングできます。

    Kotlin

        onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))))
        

    Java

        onView(allOf(withId(R.id.button_signin), not(withText("Sign-out"))));
        

    これらのメソッドをテストで使用するには、org.hamcrest.Matchers パッケージをインポートします。Hamcrest マッチングの詳細については、Hamcrest のサイトをご覧ください。

Espresso テストのパフォーマンスを改善するには、ターゲット ビューを見つけるために必要な最低限のマッチング情報を指定します。たとえば、ビューが説明テキストによって一意に識別できるのであれば、TextView インスタンスからも割り当て可能であるという情報を指定する必要はありません。

AdapterView でビューを検索する

AdapterView ウィジェットでは、実行時に子ビューが動的にビューに入力されます。テストしたいターゲット ビューが AdapterView の内部にある場合(ListViewGridViewSpinner など)、ビューのサブセットのみが現在のビュー階層に読み込まれるため、onView() メソッドが機能しないことがあります。

その場合は、onData() メソッドを呼び出して、ターゲット ビュー要素にアクセスするための DataInteraction オブジェクトを取得します。Espresso は、ターゲット ビュー要素をその時点のビュー階層に読み込む処理を行います。また、ターゲット要素までスクロールし、要素にフォーカスを合わせる処理も行います。

: onData() メソッドは、指定されたアイテムがビューに該当するかどうかをチェックしません。Espresso は、現在のビュー階層のみを検索します。一致するものが見つからない場合、メソッドからは NoMatchingViewException がスローされます。

次のコード スニペットは、onData() メソッドと Hamcrest マッチングを一緒に使用して、指定された文字列を含むリスト内の特定の行を検索する方法を示しています。この例では、SimpleAdapter を通じて公開された文字列のリストが LongListActivity クラスに含まれます。

Kotlin

    onData(allOf(`is`(instanceOf(Map::class.java)),
            hasEntry(equalTo(LongListActivity.ROW_TEXT),
            `is`("test input"))))
    

Java

    onData(allOf(is(instanceOf(Map.class)),
            hasEntry(equalTo(LongListActivity.ROW_TEXT), is("test input"))));
    

アクションを実行する

ViewInteraction.perform() または DataInteraction.perform() メソッドを呼び出して、UI コンポーネントでのユーザー インタラクションをシミュレートします。1 つ以上の ViewAction オブジェクトを引数として渡す必要があります。Espresso は、指定された順序に従って各アクションを順番に起動し、メインスレッドで実行します。

ViewActions クラスは、一般的なアクションを指定するためのヘルパー メソッドのリストを提供します。個々の ViewAction オブジェクトを作成および構成する代わりに、それらのメソッドを便利なショートカットとして使用できます。次のようなアクションの指定が可能です。

ターゲット ビューが ScrollView の内部にある場合は、他のアクションに進む前に、まず ViewActions.scrollTo() アクションを実行して画面にビューを表示します。ビューがすでに表示されている場合、ViewActions.scrollTo() アクションは効果がありません。

Espresso Intents を使用して隔離されたアクティビティをテストする

Espresso Intents は、アプリから送信されたインテントの検証とスタブ化を可能にします。Espresso Intents により、発信インテントをインターセプトし、結果をスタブ化してテスト対象のコンポーネントに送り返すことで、アプリ、アクティビティ、サービスを隔離された状態でテストできます。

Espresso Intents でテストを開始するには、アプリの build.gradle ファイルに次の行を追加する必要があります。

    dependencies {
      androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
    }
    

インテントをテストするには、IntentsTestRule クラスのインスタンスを作成する必要があります。このクラスは、ActivityTestRule クラスと類似しています。IntentsTestRule クラスは、各テストの前に Espresso Intetnts を初期化してホスト アクティビティを終了し、各テストの後に Espresso Intetnts を解放します。

次のコード スニペットに示されているテストクラスでは、明示的なインテントを対象に簡単なテストを行います。具体的には、初めてのアプリを作成するチュートリアルで作成したアクティビティとインテントをテストします。

Kotlin

    private const val MESSAGE = "This is a test"
    private const val PACKAGE_NAME = "com.example.myfirstapp"

    @RunWith(AndroidJUnit4::class)
    class SimpleIntentTest {

        /* Instantiate an IntentsTestRule object. */
        @get:Rule
        var intentsRule: IntentsTestRule<MainActivity> = IntentsTestRule(MainActivity::class.java)

        @Test
        fun verifyMessageSentToMessageActivity() {

            // Types a message into a EditText element.
            onView(withId(R.id.edit_message))
                    .perform(typeText(MESSAGE), closeSoftKeyboard())

            // Clicks a button to send the message to another
            // activity through an explicit intent.
            onView(withId(R.id.send_message)).perform(click())

            // Verifies that the DisplayMessageActivity received an intent
            // with the correct package name and message.
            intended(allOf(
                    hasComponent(hasShortClassName(".DisplayMessageActivity")),
                    toPackage(PACKAGE_NAME),
                    hasExtra(MainActivity.EXTRA_MESSAGE, MESSAGE)))

        }
    }
    

Java

    @Large
    @RunWith(AndroidJUnit4.class)
    public class SimpleIntentTest {

        private static final String MESSAGE = "This is a test";
        private static final String PACKAGE_NAME = "com.example.myfirstapp";

        /* Instantiate an IntentsTestRule object. */
        @Rule
        public IntentsTestRule<MainActivity> intentsRule =
                new IntentsTestRule<>(MainActivity.class);

        @Test
        public void verifyMessageSentToMessageActivity() {

            // Types a message into a EditText element.
            onView(withId(R.id.edit_message))
                    .perform(typeText(MESSAGE), closeSoftKeyboard());

            // Clicks a button to send the message to another
            // activity through an explicit intent.
            onView(withId(R.id.send_message)).perform(click());

            // Verifies that the DisplayMessageActivity received an intent
            // with the correct package name and message.
            intended(allOf(
                    hasComponent(hasShortClassName(".DisplayMessageActivity")),
                    toPackage(PACKAGE_NAME),
                    hasExtra(MainActivity.EXTRA_MESSAGE, MESSAGE)));

        }
    }
    

Espresso Intents の詳細については、AndroidX Test サイトの Espresso Intents ドキュメントをご覧ください。コードサンプルの IntentsBasicSampleIntentsAdvancedSample をダウンロードすることもできます。

Espresso Web で WebView をテストする

Espresso Web により、アクティビティ内に含まれる WebView コンポーネントをテストできます。Espresso Web は、WebDriver API を使用して WebView の動作を検査および制御します。

Espresso Web でテストを開始するには、アプリの build.gradle ファイルに次の行を追加する必要があります。

    dependencies {
      androidTestImplementation 'androidx.test.espresso:espresso-web:3.1.0'
    }
    

Espresso Web を使用してテストを作成する場合、ActivityTestRule オブジェクトをインスタンス化してアクティビティをテストする際に、WebView で JavaScript を有効にする必要があります。テストでは、WebView に表示される HTML 要素を選択し、テキストボックスにテキストを入力してボタンをクリックするなどのユーザー インタラクションをシミュレートできます。アクションが完了したら、ウェブページ上の結果が予期される結果と一致するかどうか検証できます。

次のコード スニペットのクラスは、テスト対象のアクティビティの ID 値が「webview」である WebView コンポーネントをテストします。typeTextInInput_clickButton_SubmitsForm() テストは、ウェブページ上の <input> 要素を選択し、テキストを入力して、別の要素に表示されるテキストをチェックします。

Kotlin

    private const val MACCHIATO = "Macchiato"
    private const val DOPPIO = "Doppio"

    @LargeTest
    @RunWith(AndroidJUnit4::class)
    class WebViewActivityTest {

        @get:Rule
        val activityRule = object : ActivityTestRule<WebViewActivity>(
                WebViewActivity::class.java,
                false,      /* Initial touch mode */
                false       /* launch activity */
        ) {
            override fun afterActivityLaunched() {
                // Enable JavaScript.
                onWebView().forceJavascriptEnabled()
            }
        }

        @Test
        fun typeTextInInput_clickButton_SubmitsForm() {
            // Lazily launch the Activity with a custom start Intent per test
            activityRule.launchActivity(withWebFormIntent())

            // Selects the WebView in your layout.
            // If you have multiple WebViews you can also use a
            // matcher to select a given WebView, onWebView(withId(R.id.web_view)).
            onWebView()
                    // Find the input element by ID
                    .withElement(findElement(Locator.ID, "text_input"))
                    // Clear previous input
                    .perform(clearElement())
                    // Enter text into the input element
                    .perform(DriverAtoms.webKeys(MACCHIATO))
                    // Find the submit button
                    .withElement(findElement(Locator.ID, "submitBtn"))
                    // Simulate a click via JavaScript
                    .perform(webClick())
                    // Find the response element by ID
                    .withElement(findElement(Locator.ID, "response"))
                    // Verify that the response page contains the entered text
                    .check(webMatches(getText(), containsString(MACCHIATO)))
        }
    }
    

Java

    @LargeTest
    @RunWith(AndroidJUnit4.class)
    public class WebViewActivityTest {

        private static final String MACCHIATO = "Macchiato";
        private static final String DOPPIO = "Doppio";

        @Rule
        public ActivityTestRule<WebViewActivity> activityRule =
            new ActivityTestRule<WebViewActivity>(WebViewActivity.class,
                false /* Initial touch mode */, false /*  launch activity */) {

            @Override
            protected void afterActivityLaunched() {
                // Enable JavaScript.
                onWebView().forceJavascriptEnabled();
            }
        }

        @Test
        public void typeTextInInput_clickButton_SubmitsForm() {
           // Lazily launch the Activity with a custom start Intent per test
           activityRule.launchActivity(withWebFormIntent());

           // Selects the WebView in your layout.
           // If you have multiple WebViews you can also use a
           // matcher to select a given WebView, onWebView(withId(R.id.web_view)).
           onWebView()
               // Find the input element by ID
               .withElement(findElement(Locator.ID, "text_input"))
               // Clear previous input
               .perform(clearElement())
               // Enter text into the input element
               .perform(DriverAtoms.webKeys(MACCHIATO))
               // Find the submit button
               .withElement(findElement(Locator.ID, "submitBtn"))
               // Simulate a click via JavaScript
               .perform(webClick())
               // Find the response element by ID
               .withElement(findElement(Locator.ID, "response"))
               // Verify that the response page contains the entered text
               .check(webMatches(getText(), containsString(MACCHIATO)));
        }
    }
    

Espresso Web の詳細については、AndroidX Test サイトの Espresso Web ドキュメントをご覧ください。Espresso Web コードサンプルの一部であるこのコード スニペットをダウンロードすることもできます。

結果を検証する

ViewInteraction.check() または DataInteraction.check() メソッドを呼び出して、UI のビューが予期される状態と一致することに対してアサーションを行います。引数として ViewAssertion オブジェクトを渡す必要があります。アサーションに失敗すると、Espresso は AssertionFailedError をスローします。

ViewAssertions クラスは、一般的なアサーションを指定するためのヘルパー メソッドのリストを提供します。次のようなアサーションを使用できます。

  • doesNotExist: 指定された条件に一致するビューが、その時点のビュー階層にはないことに対して、アサーションを行います。
  • matches: 指定されたビューがその時点のビュー階層に存在し、その状態が指定された Hamcrest マッチャーに一致することに対して、アサーションを行います。
  • selectedDescendentsMatch: 親ビューに対して指定された子ビューが存在し、子ビューの状態が指定された Hamcrest マッチャーに一致することに対して、アサーションを行います。

次のコード スニペットは、UI に表示されるテキストが、EditText フィールドに入力されたテキストと同じ値かどうかチェックする方法を示しています。

Kotlin

    fun testChangeText_sameActivity() {
        // Type text and then press the button.
        ...

        // Check that the text was changed.
        onView(withId(R.id.textToBeChanged))
                .check(matches(withText(STRING_TO_BE_TYPED)))
    }
    

Java

    public void testChangeText_sameActivity() {
        // Type text and then press the button.
        ...

        // Check that the text was changed.
        onView(withId(R.id.textToBeChanged))
                .check(matches(withText(STRING_TO_BE_TYPED)));
    }
    

デバイスまたはエミュレータで Espresso テストを実行する

Espresso テストは、Android Studio またはコマンドラインから実行できます。プロジェクトで、デフォルトのインストゥルメンテーション ランナーとして AndroidJUnitRunner を指定してください。

Espresso テストを実行するには、テストのスタートガイドで説明されているインストゥルメント化テストの実行手順に従います。

Espresso API リファレンスもお読みください。

参考情報

Android テストで UI Automator を使用する方法の詳細については、次のリソースをご覧ください。

サンプル

コードラボ