Espresso の基本

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

Espresso API を使用すると、ユーザーがアプリを操作する中で行うこと(UI 要素を見つけて操作する)を考えながらテストを作成できます。また、Espresso フレームワークでは、アプリのアクティビティとビューに直接アクセスできないようになっています。このようなオブジェクトを UI スレッド外で保持して操作すると、テストの信頼性が失われるおそれがあるためです。したがって、Espresso API には getView()getCurrentActivity() のようなメソッドはありません。 そうしたメソッドがなくても、ViewActionViewAssertion の独自のサブクラスを実装することにより、ビューを安全に操作できます。

API コンポーネント

Espresso の主なコンポーネントは次のとおりです。

  • EspressoonView()onData() を介してビューとやり取りするためのエントリ ポイント。必ずしもビューに結び付けられていない API も公開します(pressBack() など)。
  • ViewMatchers - Matcher<? super View> インターフェースを実装するオブジェクトのコレクション。この中の 1 つ以上のオブジェクトを onView() メソッドに渡して、現在のビュー階層内のビューを特定します。
  • ViewAction - click() など、ViewInteraction.perform() メソッドに渡すことができる ViewAction オブジェクトのコレクション。
  • 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 マッチャーが使用されます。このマッチャーにより、現在のビュー階層内でビューが 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 を指定すれば十分です。
  • 対象のビューが ListViewGridViewSpinner などのように 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!" を含むビューが表示されているかどうかをアサーションする場合(たとえば、ビュー表示フラグを変更した後など)は、このコードで問題ありません。

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

この例では、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 のカスタム実装では、継承コントラクトに違反すると、onData() メソッドで問題が発生する可能性があります(特に getItem() API の場合)。このケースでは、アプリコードのリファクタリングが最善の措置です。それができない場合は、カスタムのマッチング 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 のテストに関するその他の情報については、次のリソースをご覧ください。

サンプル