Espresso の基本

このドキュメントでは、Espresso API を使用して一般的な自動テストタスクを行う方法について説明します。

Espresso API を使用すると、テスト作成者は、アプリを操作する際にユーザーが行う可能性のある操作(UI 要素を見つけて操作する)について考えるよう促すことができます。同時に、フレームワークはアプリのアクティビティとビューに直接アクセスできません。これは、これらのオブジェクトを保持し、UI スレッド外で操作すると、テストの不安定性の主な原因になるためです。したがって、Espresso API には getView()getCurrentActivity() のようなメソッドはありません。ViewActionViewAssertion の独自のサブクラスを実装することで、引き続きビューを安全に操作できます。

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 で十分です。
  • ターゲット ビューが AdapterViewListViewGridViewSpinner など)内にある場合、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!" というテキストを含むビューの表示を(たとえば、ビューの公開設定フラグを変更した後)アサートする場合、このコードを使用しても問題ありません。

ビュー アサーションの簡単なテスト

この例では、SimpleActivityButtonTextView が含まれています。ボタンがクリックされると、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 を使用する方法について詳しくは、以下のリソースをご覧ください。

サンプル