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.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!")));

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

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), GridView 或 Spinner:onView() 方法可能無法運作。在以下 則應改用 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 包含 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 可能發生與「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()時 作業會擲回 NoMatchingViewException 和 AdapterView 小工具 中,最常見的解決方案是使用 onData()。 例外狀況訊息會包含內含轉接器檢視畫面清單的警告。 您可以使用這項資訊叫用 onData() 載入目標檢視畫面。

其他資源

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

範例