単一アプリ内のユーザー インタラクションをテストして、ユーザーがアプリを操作する際に、予期せぬ結果に遭遇したり、ユーザー エクスペリエンスの問題が生じたりしないことを確認します。アプリの 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 テストを作成するには、次のプログラミング モデルに従います。
-
onView()
メソッドまたはAdapterView
コントロールのonData()
メソッドを呼び出して、Activity
のテストを行う UI コンポーネント(アプリのログインボタンなど)を見つけます。 -
ViewInteraction.perform()
メソッドまたはDataInteraction.perform()
メソッドを呼び出してユーザー アクション(ログインボタンのクリックなど)を渡すことにより、その UI コンポーネントで実行する特定のユーザー インタラクションをシミュレートします。同じ UI コンポーネントで複数のアクションを連続して行うには、メソッド引数にカンマ区切りリストを使用してアクションのチェーンを指定します。 - 必要に応じて上記の手順を繰り返し、ターゲット アプリの複数のアクティビティで構成されるユーザーフローをシミュレートします。
-
ユーザー インタラクションが実行された後、
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
の内部にある場合(ListView
、GridView
、Spinner
など)、ビューのサブセットのみが現在のビュー階層に読み込まれるため、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
オブジェクトを作成および構成する代わりに、それらのメソッドを便利なショートカットとして使用できます。次のようなアクションの指定が可能です。
-
ViewActions.click()
: ビューをクリックします。 -
ViewActions.typeText()
: ビューをクリックして、指定された文字列を入力します。 -
ViewActions.scrollTo()
: ビューまでスクロールします。ターゲット ビューはScrollView
からサブクラス化される必要があり、そのandroid:visibility
プロパティの値はVISIBLE
でなくてはなりません。AdapterView
を拡張するビュー(ListView
など)では、onData()
メソッドがスクロールを処理します。 -
ViewActions.pressKey()
: 指定されたキーコードを使用してキーを押します。 -
ViewActions.clearText()
: ターゲット ビューのテキストをクリアします。
ターゲット ビューが 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 ドキュメントをご覧ください。 コードサンプルの IntentsBasicSample と IntentsAdvancedSample をダウンロードすることもできます。
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 を使用する方法の詳細については、次のリソースをご覧ください。
サンプル
- Espresso コードサンプル: Espresso サンプルの完全なコレクションがあります。