このドキュメントでは、Espresso API を使用して一般的な自動テストタスクを行う方法について説明します。
Espresso API を使用すると、テスト作成者は、アプリを操作する際にユーザーが行う可能性のある操作(UI 要素を見つけて操作する)について考えるよう促すことができます。同時に、フレームワークはアプリのアクティビティとビューに直接アクセスできません。これは、これらのオブジェクトを保持し、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
オブジェクトのコレクション。ほとんどの場合は、マッチ アサーションを使用します。このアサーションは、ビュー マッチャーを使用して、現在選択されているビューの状態をアサートします。
例:
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 マッチャーを使用します。このマッチャーでは、現在のビュー階層内のビューは 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()
を使用して検索を絞り込めない場合は、ユーザー補助のバグとして扱うことを検討してください。 - 探しているビューを見つけるうえで、最も説明が不要なマッチャーを使用します。フレームワークが必要以上の処理を強いられるため、指定しすぎないでください。たとえば、ビューがそのテキストで一意に識別できる場合、そのビューも
TextView
から割り当て可能であることを指定する必要はありません。多くのビューはR.id
で十分です。 - ターゲット ビューが
AdapterView
(ListView
、GridView
、Spinner
など)内にある場合、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
(垂直または水平)内にある場合は、scrollTo()
を使用して、ビューの表示を必要とするアクション(click()
や typeText()
など)を検討してください。これにより、他のアクションに進む前にビューが表示されるようになります。
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
ビュー階層
onView()
が失敗すると、Espresso は例外メッセージにビュー階層を出力します。
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()
を呼び出し、目的のビューを読み込みます。
参考情報
Android のテストで Espresso を使用する方法について詳しくは、以下のリソースをご覧ください。
サンプル
- CustomMatcherSample:
EditText
オブジェクトの Hint プロパティと一致するように Espresso を拡張する方法を示します。 - RecyclerViewSample: Espresso の
RecyclerView
アクション。 - (その他)