このドキュメントでは、Espresso API を使用して一般的な自動テストタスクを実施する方法について説明します。
Espresso API を使用すると、ユーザーがアプリとやり取りする中で行うこと(UI 要素を見つけて操作する)を考えながらテストを作成できます。また、Espresso フレームワークでは、アプリのアクティビティとビューに直接アクセスできないようになっています。これは、このようなオブジェクトを UI スレッド以外から直接操作すると、テストが不安定になるおそれがあるためです。したがって、Espresso API には getView()
や getCurrentActivity()
などのメソッドはありません。それでも、ViewAction
や ViewAssertion
で独自のサブクラスを実装することにより、ビューを安全に操作できます。
API コンポーネント
Espresso の主なコンポーネントは次のとおりです。
- Espresso – ビューとやり取り(
onView()
やonData()
を介して)するためのエントリ ポイント。表示される API は、必ずしもビューに関連付くものとは限りません(pressBack()
など)。 - ViewMatchers –
Matcher<? super View>
インターフェースを実装するオブジェクトのコレクション。この中の 1 つ以上をonView()
メソッドに渡して、現在のビュー階層の中でのビューの場所を特定できます。 - ViewActions –
ViewInteraction.perform()
メソッドに渡すことのできるViewAction
オブジェクトのコレクション(click()
など)。 - ViewAssertions –
ViewInteraction.check()
メソッドに渡すことのできるViewAssertion
オブジェクトのコレクション。多くの場合、使用するのは matches アサーションで、ビュー マッチャーを使って選択中のビューの状態に対してアサーションを行います。
例:
Kotlin
// withId(R.id.my_view) is a ViewMatcher // click() is a ViewAction // matches(isDisplayed()) is a ViewAssertion onView(withId(R.id.my_view)) .perform(click()) .check(matches(isDisplayed()))
Java
// withId(R.id.my_view) is a ViewMatcher // click() is a ViewAction // matches(isDisplayed()) is a ViewAssertion onView(withId(R.id.my_view)) .perform(click()) .check(matches(isDisplayed()));
ビューの特定
ほとんどの場合、onView()
メソッドでは Hamcrest マッチャーが使用されます。通常は、Hamcrest マッチャーによりビュー階層内でビューが 1 つに絞り込まれます。マッチャーは非常に有効で、Mockito や JUnit の使用経験があればすぐに使いこなせます。Hamcrest マッチャーになじみがない場合は、このプレゼンテーションをまずご覧ください。
たいていの場合、目的のビューには一意の R.id
があり、単純な withId
マッチャーでビューを絞り込めます。しかし、テスト開発時には、当然のこととして R.id
を特定できないケースが多くあります。たとえば、あるビューには R.id
がなかったり、あっても一意の R.id
でなかったりします。このような場合は、ビューにアクセスするために通常使用する findViewById()
が機能しないため、標準的なインストゥルメンテーション テストの作成が複雑で困難になります。場合によっては、ビューを保持するアクティビティやフラグメントのプライベート メンバーにアクセスしたり、既知の R.id
を持つコンテナを見つけて、そこから特定のビューのコンテンツに移動したりする必要があります。
Espresso では、ビューの絞り込みに、既存の ViewMatcher
オブジェクトや独自のカスタム オブジェクトのいずれかを使用できるようにすることで、この問題を解決しています。
R.id
によるビューの特定は、onView()
を呼び出すことで簡単に行えます。
Kotlin
onView(withId(R.id.my_view))
Java
onView(withId(R.id.my_view));
複数のビューが同じ R.id
値を持つ場合があります。このような状況で、その R.id
を使用すると、AmbiguousViewMatcherException
などの例外が発生します。例外メッセージには、現在のビュー階層がテキストで表示されており、一意でない R.id
と一致する複数のビューを検索により探し出せます。
java.lang.RuntimeException: androidx.test.espresso.AmbiguousViewMatcherException This matcher matches multiple views in the hierarchy: (withId: is <123456789>) ... +----->SomeView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=false, enabled=true, selected=false, is-layout-requested=false, text=, root-is-layout-requested=false, x=0.0, y=625.0, child-count=1} ****MATCHES**** | +------>OtherView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=true, enabled=true, selected=false, is-layout-requested=false, text=Hello!, root-is-layout-requested=false, x=0.0, y=0.0, child-count=1} ****MATCHES****
表示されたビューのさまざまな属性を調べると、一意に特定可能なプロパティが見つかる場合があります。上記の例では、一方のビューに "Hello!"
というテキストが入っています。これを利用すれば、次のようにしてマッチャーの組み合わせによる絞り込み検索が可能です。
Kotlin
onView(allOf(withId(R.id.my_view), withText("Hello!")))
Java
onView(allOf(withId(R.id.my_view), withText("Hello!")));
どのマッチャーも、not 関数を使用することで、値を反転させることができます。
Kotlin
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))
Java
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))));
Espresso で利用できるビュー マッチャーについては、ViewMatchers
をご覧ください。
留意事項
- アプリを正しく動作させるためには、ユーザーが操作できるすべてのビューに、説明となるテキストまたはコンテンツの説明文を含める必要があります。詳細については、アプリをより使いやすくする方法をご覧ください。
withText()
やwithContentDescription()
を使用してビューを絞り込めない場合は、アプリの使いやすさに問題があると考えてください。 - 必要なビューを検索できるようにするためにマッチャーに説明を付ける際は、必要最低限のものにしてください。Espresso フレームワークに不要な負荷がかかるため、過度な指定を避けます。たとえば、ビューがそのテキストで一意に特定できるのであれば、
TextView
から割り当て可能であることまで指定する必要はありません。多くのビューでは、R.id
を指定すれば十分です。 - 対象のビューが
ListView
、GridView
、Spinner
などAdapterView
の内部にあると、onView()
メソッドが機能しないことがあります。このような場合は、代わりにonData()
を使用する必要があります。
ビューでのアクションの実施
対象のビューに適合するマッチャーが見つかったら、perform メソッドを使用して、そのビューで ViewAction
インスタンスを実施できます。
たとえば、次のようにしてそのビューをクリックします。
Kotlin
onView(...).perform(click())
Java
onView(...).perform(click());
次のように、1 回の perform 呼び出しで複数のアクションを実施できます。
Kotlin
onView(...).perform(typeText("Hello"), click())
Java
onView(...).perform(typeText("Hello"), click());
作業対象のビューが ScrollView
(垂直または水平)内にある場合は、click()
や typeText()
など、ビューを表示しておく必要があるアクションの前に、scrollTo()
を使用するようにしてください。次のようにして、ビューが確実に表示されてからその他のアクションに進みます。
Kotlin
onView(...).perform(scrollTo(), click())
Java
onView(...).perform(scrollTo(), click());
Espresso で利用できるビュー アクションについては、ViewActions
をご覧ください。
ビュー アサーションの確認
check()
メソッドを使用すると、選択中のビューにアサーションを適用できます。よく使用されるのは matches()
アサーションです。これは、ViewMatcher
オブジェクトを使用して、選択中のビューの状態に対してアサーションを行うものです。
たとえば、ビューにテキスト "Hello!"
が含まれることを確認するには次のようにします。
Kotlin
onView(...).check(matches(withText("Hello!")))
Java
onView(...).check(matches(withText("Hello!")));
ビューのコンテンツに "Hello!"
が含まれているかを、アサーションで確認する場合の不適切な例を次に示します。
Kotlin
// Don't use assertions like withText inside onView. onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()))
Java
// Don't use assertions like withText inside onView. onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()));
ただし、テキスト "Hello!"
を含むビューが表示されていることをアサーションで確認する場合(たとえば、ビューの表示フラグを変更した後など)であれば、このコードで問題ありません。
ビュー アサーションの簡単なテスト
この例では、SimpleActivity
には Button
と TextView
が含まれています。ボタンがクリックされると、TextView
のコンテンツが "Hello Espresso!"
に変わります。
Espresso では、これを次のようにテストします。
ボタンのクリック
最初のステップとして、ボタンの特定に役立つプロパティを探します。SimpleActivity
内のボタンに一意の R.id
がある場合は、次のようにします。
Kotlin
onView(withId(R.id.button_simple))
Java
onView(withId(R.id.button_simple));
次のようにすると、クリックが行えます。
Kotlin
onView(withId(R.id.button_simple)).perform(click())
Java
onView(withId(R.id.button_simple)).perform(click());
TextView のテキストの確認
確認対象のテキストを含む TextView
にも一意の R.id
がある場合は、次のようにします。
Kotlin
onView(withId(R.id.text_simple))
Java
onView(withId(R.id.text_simple));
次のようにすると、コンテンツのテキストが確認できます。
Kotlin
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")))
Java
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));
アダプター ビューでのデータ読み込みの確認
AdapterView
は、データをアダプターから動的に読み込む特別なタイプのウィジェットです。AdapterView
の最も一般的な例は ListView
です。LinearLayout
のような静的なウィジェットとは異なり、AdapterView
では、タイミングによってビュー階層に読み込まれているのが子要素のサブセットだけである場合があります。通常の onView()
検索では、検索の時点で読み込まれていないビューは見つけられません。
Espresso では、別途 onData()
エントリ ポイントを用意することでこの問題を解決しています。該当するアダプター項目を先に読み込んでおき、アダプターやその子要素に対する操作に先だって、対象に含めておくことができます。
警告: AdapterView
のカスタム実装では、継承コントラクト(特に getItem()
API)を破ると、onData()
メソッドで問題が発生する可能性があります。この場合の処置としては、アプリのコードをリファクタリングするのが最適です。それができない場合は、マッチング カスタム AdapterViewProtocol
を実装します。詳細については、Espresso に用意されているデフォルトの AdapterViewProtocols
クラスをご覧ください。
アダプター ビューの簡単なテスト
ここでは、簡単なテストにより onData()
の使用方法を説明します。SimpleActivity
には、コーヒー飲料の種類を表す項目をいくつか含んだ Spinner
があります。ある項目を選択すると、そこにある TextView
の内容が "One %s a day!"
に変わります(ここで、%s
は選択した項目を表します)。
このテストでは、Spinner
を開いて特定の項目を選択した後、TextView
にその項目が含まれていることを確認します。Spinner
クラスは AdapterView
がベースのため、項目のマッチングには onView()
ではなく onData()
を使用することが推奨されます。
項目の選択肢の表示
Kotlin
onView(withId(R.id.spinner_simple)).perform(click())
Java
onView(withId(R.id.spinner_simple)).perform(click());
項目の選択
項目を選択できるよう、Spinner
でコンテンツから ListView
が作成されます。このビューは非常に長くなる可能性があり、要素がビュー階層に含まれないことも考えられます。そのため、onData()
を使用して目的の要素を強制的にビュー階層に含めます。Spinner
内の項目は文字列のため、次のようにして文字列 "Americano"
と等しい項目にマッチさせます。
Kotlin
onData(allOf(`is`(instanceOf(String::class.java)), `is`("Americano"))).perform(click())
Java
onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());
テキストが正しいことの確認
Kotlin
onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))))
Java
onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))));
デバッグ
テストが失敗した場合、Espresso からデバッグに役立つ情報を入手できます。
ログ
Espresso では、すべてのビュー アクションが logcat に記録されます。例:
ViewInteraction: Performing 'single click' action on view with text: Espresso
ビュー階層
Espresso では、onView()
が失敗した場合、例外メッセージの中にビュー階層が示されます。
onView()
で目的のビューが見つからなかった場合は、NoMatchingViewException
がスローされます。例外メッセージ中のビュー階層を調べることにより、マッチャーに一致するビューが確認できなかった理由を分析できます。onView()
で、マッチャーに一致するビューが複数見つかった場合は、AmbiguousViewMatcherException
がスローされます。ビュー階層とともに、一致したすべてのビューがMATCHES
ラベル付きで示されます。
java.lang.RuntimeException: androidx.test.espresso.AmbiguousViewMatcherException This matcher matches multiple views in the hierarchy: (withId: is <123456789>) ... +----->SomeView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=false, enabled=true, selected=false, is-layout-requested=false, text=, root-is-layout-requested=false, x=0.0, y=625.0, child-count=1} ****MATCHES**** | +------>OtherView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true, is-focused=false, is-focusable=true, enabled=true, selected=false, is-layout-requested=false, text=Hello!, root-is-layout-requested=false, x=0.0, y=0.0, child-count=1} ****MATCHES****
複雑なビュー階層やウィジェットの想定外の動作に対処する場合、Android Studio の Hierarchy Viewer を使用すると有用な情報が得られます。
アダプター ビューの警告
Espresso では、ユーザーに対して AdapterView
ウィジェットの存在を示す警告が示されます。onView()
操作の結果、NoMatchingViewException
がスローされ、ビュー階層に AdapterView
ウィジェットが存在する場合は、onData()
を使用することで通常は解決できます。例外メッセージには、アダプター ビューのリストを示す警告が含まれています。この情報を使用して onData()
を呼び出し、目的のビューを読み込みます。
参考情報
Espresso を使用した Android のテストに関するその他の情報については、次のリソースをご覧ください。
サンプル
- CustomMatcherSample:
EditText
オブジェクトの hint プロパティとマッチングするように Espresso の機能を拡張する方法を示しています。 - RecyclerViewSample: Espresso の
RecyclerView
アクション。 - その他のサンプル