Espresso 基本概念

本文件將說明如何使用 Espresso API。

Espresso API 鼓勵測試作者從使用者可能想看的內容中思考 例如找出 UI 元素並和使用者互動 看似有益或無害的 AI 用途 也可能夾帶問題或相關風險同時,該架構也能防止直接存取活動 都會保留這些物件, 是測試不穩定的主要來源。因此,您 不會在 Espresso API 中看到 getView()getCurrentActivity() 等方法。 您可以實作自己的 子類別,安全地在檢視畫面上執行作業 《ViewAction》和《ViewAssertion》。

API 元件

Espresso 的主要元件包括:

  • Espresso - 與檢視畫面互動的進入點 (透過 onView()onData())。此外,也會公開不一定與任何檢視畫面相連結的 API,例如 格式:pressBack()
  • ViewMatchers - 實作 Matcher<? super View> 介面。您可以將一或多項這類訊息傳送至 onView() 方法可在目前的檢視區塊階層中找到檢視區塊。
  • ViewActions:可傳遞到的 ViewAction 物件集合 ViewInteraction.perform() 方法,例如 click()
  • ViewAssertions - 可自訂的 ViewAssertion 物件集合 已傳遞 ViewInteraction.check() 方法。在大多數的情況下,您僅會使用 比對斷言,斷言會使用 View 比對器,宣告 目前所選的資料檢視

例子:

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() 方法會採用罕見比對器 預期比對項目會與目前檢視畫面中一個項目相符,但只有一個項目 階層比對器的功能很強大,且過去曾使用 透過 Mockito 或 JUnit如果不熟悉「倉鼠比對器」 建議您先看一下這裡 簡報

一般來說,想要的檢視畫面都有專屬的 R.id,以及簡單的 withId 比對器會 縮小搜尋範圍然而,在處理工作負載時 無法在測試開發期間判斷 R.id。舉例來說 沒有 R.idR.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!")));

您也可以選擇不反轉任何比對器:

Kotlin

onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))

Java

onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))));

查看「ViewMatchers」 一個關於 Espresso 提供的檢視比對器

注意事項

  • 在運作良好的應用程式中,使用者可進行互動的所有檢視畫面 應包含描述性文字或內容說明。詳情請見 打造更符合無障礙需求的應用程式 詳細資料。如果無法使用 withText()withContentDescription(),建議將這個問題視為無障礙功能錯誤。
  • 使用最少描述性的比對器來尋找所需的檢視畫面 。請勿過度指定,因為這將迫使架構執行超出 。舉例來說,如果檢視畫面的文字能明確識別,您可以 不需要指定檢視畫面也可從 TextView 指派。許多 檢視畫面的 R.id 應該就夠用了。
  • 如果目標檢視畫面位於 AdapterView 內 (例如 ListView), GridViewSpinneronView() 方法可能無法運作。在以下 則應改用 onData()

在檢視畫面上執行操作

當您找到了適合目標檢視的比對器後,就可以 使用執行方法,對其執行 ViewAction 的執行個體。

舉例來說,如要點選檢視畫面:

Kotlin

onView(...).perform(click())

Java

onView(...).perform(click());

您可以透過單一執行呼叫來執行多個動作:

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());

查看「ViewActions」 查看 Espresso 提供的觀看動作

查看檢視畫面斷言

可以使用 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 包含 ButtonTextView。當 當使用者按一下按鈕,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。如需更多資訊,請 看看預設的 AdapterViewProtocols 類別 (由 Espresso 提供)。

轉接程式檢視畫面簡易測試

這個簡單的測試示範如何使用 onData()SimpleActivity 包含 Spinner,其中幾個項目代表咖啡飲料類型。如果 已選取項目,就會有 TextView 變更為 "One %s a day!",其中 %s 表示所選項目。

這項測試的目標在於開啟 Spinner 並選取特定項目,然後 確認 TextView 包含項目。由於 Spinner 類別是以 在 AdapterView,建議使用 onData(),而不是 onView(): 符合項目。

開啟項目選項

Kotlin

onView(withId(R.id.spinner_simple)).perform(click())

Java

onView(withId(R.id.spinner_simple)).perform(click());

選取項目

針對選取項目,Spinner 會建立含有其內容的 ListView。 這個檢視畫面可能很長,且該元素可能無法為檢視畫面貢獻內容 階層使用 onData() 時,我們會將所需元素強制入檢視畫面中 階層Spinner 中的項目是字串,因此我們想比對一個項目 等於 String "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 中的階層檢視器: 解釋

轉接程式檢視畫面警告

Espresso 會警告使用者有 AdapterView 小工具,出現onView()時 作業會擲回 NoMatchingViewExceptionAdapterView 小工具 中,最常見的解決方案是使用 onData()。 例外狀況訊息會包含內含轉接器檢視畫面清單的警告。 您可以使用這項資訊叫用 onData() 載入目標檢視畫面。

其他資源

如要進一步瞭解如何在 Android 測試中使用 Espresso,請參閱 資源。

範例