聲明:本系列文章是對(duì) Android Testing Support Library官方文檔的翻譯粱年,水平有限了袁,歡迎批評(píng)指正酌儒。
1. Espresso 概覽
2. Espresso 設(shè)置說(shuō)明
3. Espresso 基礎(chǔ)
4. Espresso 備忘錄
5. Espresso 意圖
6. Espresso 高級(jí)示例
7. Espresso Web
8. AndroidJUnitRunner
9. ATSL 中的 JUnit4 規(guī)則
10. UI Automator
11. 可訪問(wèn)性檢查
Espresso API 鼓勵(lì)測(cè)試者以用戶會(huì)怎樣與應(yīng)用交互的方式進(jìn)行思考來(lái)定位 UI 元素并與它們交互轧叽。同時(shí)罗洗,框架不允許直接使用應(yīng)用的活動(dòng)和視圖肛跌,因?yàn)樵诜?UI 線程持有此類(lèi)對(duì)象并對(duì)它們操作是造成測(cè)試花屏的主要原因艺配。因此,你不會(huì)在 Espresso API 中看到諸如 getView 或 getCurrentActivity 等方法衍慎。但你仍然可以通過(guò)實(shí)現(xiàn) ViewAction
和 ViewAssertion
來(lái)對(duì)視圖進(jìn)行安全操作转唉。
以下是 Espresso 主要組件的概覽:
-
Espresso - 與視圖交互的切入點(diǎn)(參考
onView
和onData
)。也暴露了與任何視圖都沒(méi)有必然聯(lián)系的 API(如?pressBack
)稳捆。 -
ViewMatchers - 實(shí)現(xiàn)了
?Matcher<? super View>
? 接口的對(duì)象集合赠法。你可以在?onView
? 方法中傳入一個(gè)或多個(gè)此類(lèi)對(duì)象來(lái)在當(dāng)前的視圖結(jié)構(gòu)中定位一個(gè)視圖。 -
ViewActions - 可以作為參數(shù)傳入
?ViewInteraction.perform()
? 方法中的ViewAction
的集合(如?click()
)乔夯。 -
ViewAssertions - 可以作為參數(shù)傳入
?ViewInteraction.check()
? 方法中的ViewAssertion
的集合砖织。通常,你會(huì)使用帶有視圖匹配器的匹配斷言來(lái)判斷當(dāng)前被選中視圖的狀態(tài)末荐。
例如:
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
使用 onView 查找視圖
多數(shù)情況下侧纯,onView 方法使用 hamcrest 匹配器以期望在當(dāng)前視圖結(jié)構(gòu)里匹配一個(gè)(唯一的)視圖。該匹配器十分強(qiáng)大而且對(duì)用過(guò) Mockito 或 JUnit 的人而言并不陌生甲脏。如果你對(duì) hamcrest 匹配器不熟悉眶熬,我們建議你先快速瀏覽一下此報(bào)告妹笆。(譯注:譯者本人表示打不開(kāi))
想要查找的視圖一般會(huì)有唯一的 ?R.id
? 值,使用簡(jiǎn)單的 ?withId
? 匹配器可以縮小搜索范圍娜氏。然而晾浴,當(dāng)你在測(cè)試開(kāi)發(fā)階段,無(wú)法確定 ?R.id
值是合理的?牍白。例如脊凰,指定的視圖可能沒(méi)有 R.id
? 值或該值不唯一。這將使一般的 instrumentation 測(cè)試變得脆弱而復(fù)雜茂腥,因?yàn)橥ㄓ玫墨@取視圖方式(通過(guò) findViewById()
)已經(jīng)不適用了狸涌。因此,你可能需要獲取持有視圖的私有對(duì)象 Activity 或 Fragment最岗,或者找到一個(gè)已知其 ?R.id
? 值的父容器帕胆,然后在其中定位到特定的視圖。
Espresso 處理該問(wèn)題的方式很干脆般渡,它允許你使用已存在的或自定義的 ViewMatcher 來(lái)限定視圖查找懒豹。
通過(guò) ?R.id
? 查找視圖:
onView(withId(R.id.my_view))
有時(shí),?R.id
?值會(huì)被多個(gè)視圖共享驯用。此時(shí)脸秽,如果嘗試使用該 ?R.id
? 值將會(huì)拋出類(lèi)似 ?AmbiguousViewMatcherException
?的異常。異常信息會(huì)給你提供文字描述形式的當(dāng)前視圖結(jié)構(gòu)蝴乔,你可以搜索并找出所有使用非唯一 ?R.id
? 值的視圖:
java.lang.RuntimeException:
com.google.android.apps.common.testing.ui.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****
通過(guò)查看視圖豐富的屬性记餐,你興許可以找到唯一可確認(rèn)的屬性(上例中,其中一個(gè)視圖有一個(gè)“Hello!”文本)薇正。你可以通過(guò)使用組合匹配器結(jié)合該屬性來(lái)縮小搜索范圍:
onView(allOf(withId(R.id.my_view), withText("Hello!")))
你也可以使用 ?not
? 反轉(zhuǎn)匹配:
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))
你可以在 ViewMatchers 類(lèi)中查看 Espresso 提供的視圖匹配器片酝。
注意:在一個(gè)良態(tài)的應(yīng)用中,所有用戶可與之交互的視圖都應(yīng)該包含說(shuō)明文字或有一個(gè)內(nèi)容描述(參考 Android 可訪問(wèn)性指導(dǎo))挖腰。如果你不能通過(guò)使用 ‘withText’ 或 ‘withContentDescripiton’ 來(lái)縮小 onView 的搜索范圍雕沿,可以認(rèn)為這是一個(gè)可訪問(wèn)性的 bug。
注意:請(qǐng)使用最少的匹配器來(lái)定位視圖猴仑。不要過(guò)指定审轮,因?yàn)檫@將強(qiáng)制框架做無(wú)用功。例如宁脊,如果一個(gè)視圖可以通過(guò)它的文字唯一確定断国,你不需要說(shuō)明該視圖也可以通過(guò) ?TextView
? 指定。對(duì)許多視圖而言榆苞,使用它的 ?R.id
? 值就足夠了稳衬。
注意:如果目標(biāo)視圖在一個(gè) ?AdapterView
?(如 ?ListView
?,?GridView
?坐漏,?Spinner
?)中薄疚,將不能使用 onView
? 方法碧信,推薦使用 ?onData
? 方法。
在視圖上執(zhí)行操作
當(dāng)為目標(biāo)視圖找到了合適的適配器后街夭,你將可以通過(guò) ?perform
? 方法在該視圖上執(zhí)行 ?ViewAction
?砰碴。
例如,點(diǎn)擊該視圖:
onView(…).perform(click());
你可以在一個(gè) perform 方法中執(zhí)行多個(gè)操作:
onView(…).perform(typeText("Hello"), click());
如果操作的視圖在 ?ScrollView
?(水平或垂直方向)中板丽,需要考慮在對(duì)該視圖執(zhí)行操作(如 ?click()
? 或 ?typeText()
?)之前通過(guò) ?scrollTo()
? 方法使其處于顯示狀態(tài)呈枉。這樣就保證了視圖在執(zhí)行其他操作之前是顯示著的。
onView(…).perform(scrollTo(), click());
注意:如果視圖已經(jīng)是顯示狀態(tài)埃碱,* *?scrollTo()
? 將不會(huì)對(duì)界面有影響猖辫。因此,當(dāng)視圖的可見(jiàn)性取決于屏幕的大小時(shí)(例如砚殿,同時(shí)在大屏和小屏上執(zhí)行測(cè)試時(shí))啃憎,你可以安全的使用該方法。
你可以在 ViewActions 類(lèi)中產(chǎn)看 Espresso 提供的視圖操作似炎。
檢查一個(gè)視圖是否滿足斷言
斷言可以通過(guò) ?check()
? 方法應(yīng)用在當(dāng)前選中的視圖上辛萍。最常用的是 ?matches()
? 斷言,它使用一個(gè) ?ViewMatcher
? 來(lái)判斷當(dāng)前選中視圖的狀態(tài)羡藐。
例如贩毕,檢查一個(gè)視圖擁有 “Hello!”文本:
onView(…).check(matches(withText("Hello!")));
注意:不要將 “assertions” 作為 onView 的參數(shù)傳入,而要在檢查代碼塊中明確指定你檢查的內(nèi)容传睹,例如:
如果你想要斷言視圖的內(nèi)容是 “Hello!” 耳幢,以下做法是反面教材:
// Don't use assertions like withText inside onView.
onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()));
從另一個(gè)角度講,如果你想要斷言一個(gè)包含 “Hello!” 文本的視圖是可見(jiàn)的(例如欧啤,在修改了該視圖的可見(jiàn)性標(biāo)志之后),這段代碼是正確的启上。
注意:請(qǐng)留意斷言一個(gè)視圖沒(méi)有顯示和斷言一個(gè)視圖不在當(dāng)前視圖結(jié)構(gòu)之間的區(qū)別邢隧。
使用 onView 編寫(xiě)一個(gè)簡(jiǎn)單的測(cè)試
在此示例中,?SimpleActivity
? 包含一個(gè) ?Button
? 和一個(gè) ?TextView
?冈在。當(dāng)點(diǎn)擊按鈕時(shí)倒慧,?TextView
? 的內(nèi)容更改為 “Hello Espresso!”。以下是如何使用 Espresso 執(zhí)行此測(cè)試的講解:
1. 點(diǎn)擊按鈕
第一步是檢索一個(gè)能定位這個(gè)按鈕的屬性包券。?SimpleActivity
? 中的這個(gè)按鈕擁有唯一的 ?R.id
?纫谅,贊!
onView(withId(R.id.button_simple))
然后執(zhí)行點(diǎn)擊操作:
onView(withId(R.id.button_simple)).perform(click());
2. 檢查 ?TextView
? 中是否包含 “Hello Espresso!”
待驗(yàn)證的 ?TextView
? 也包含唯一的 ?R.id
?:
onView(withId(R.id.text_simple))
然后驗(yàn)證文本內(nèi)容:
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));
在 ?AdapterView
? 控制器(ListView
, GridView
, ...)中使用 onData
?AdapterView
? 是一個(gè)從適配器中動(dòng)態(tài)加載數(shù)據(jù)的特殊控件溅固。最常見(jiàn)的 ?AdapterView
? 是 ListView
?付秕。與像 ?LinearLayout
? 這樣的靜態(tài)控件相反,在當(dāng)前視圖結(jié)構(gòu)中侍郭,可能只加載了 ?AdapterView
? 子控件的一部分询吴, 簡(jiǎn)單的 ?onview()
? 搜索不能找到當(dāng)前沒(méi)有被加載的視圖掠河。Espresso 通過(guò)提供單獨(dú)的 onData()
? 切入點(diǎn)處理此問(wèn)題,它可以在操作適配器中有該問(wèn)題的條目或該條目的子項(xiàng)之前將其加載(使其獲取焦點(diǎn))猛计。
注意:你可能不會(huì)對(duì)初始狀態(tài)就顯示在屏幕上的適配器條目執(zhí)行 ?onData()
? 加載操作唠摹,因?yàn)樗鼈円呀?jīng)被加載了。然而奉瘤,一直使用 ?onData()
? 會(huì)更安全勾拉。
警告:對(duì)于 AdapterView
? 的自定義實(shí)現(xiàn),如果他們打破了繼承契約(尤其是 ?getItem()
? API)盗温,使用 ?onData()
? 方法時(shí)會(huì)出現(xiàn)問(wèn)題藕赞。此種情況,最好是重構(gòu)你的應(yīng)用代碼肌访。如果不能這樣做找默,你可以實(shí)現(xiàn)一個(gè)匹配的自定義 ?AdapterViewProtocol
?。查看 Espresso 提供的默認(rèn)的 AdapterViewProtocols 獲取供多信息吼驶。
使用 onData 編寫(xiě)一個(gè)簡(jiǎn)單的測(cè)試
這個(gè)簡(jiǎn)單的測(cè)試演示了如何使用? onData()
?惩激。
?SimpleActivity
? 包含一個(gè) ?Spinner
? ,該 Spinner
? 中有幾個(gè)條目——代表咖啡類(lèi)型的字符串蟹演。當(dāng)選中其中一個(gè)條目時(shí)风钻,?TextView
? 內(nèi)容會(huì)變成 ?“One %s a day!”
?,其中 %s 代表選中的條目酒请。此測(cè)試的目標(biāo)是打開(kāi) ?Spinner
?骡技,選中一個(gè)條目然后驗(yàn)證 ?TextView
? 中包含該條目。由于 ?Spinner
? 類(lèi)基于 ?AdapterView
?羞反,建議使用 ?onData()
? 而不是 ?onView()
? 來(lái)匹配條目布朦。
1. 點(diǎn)擊 Spinner 打開(kāi)條目選擇框
onView(withId(R.id.spinner_simple)).perform(click());
2. 點(diǎn)擊 “Americano” 條目
為了條目可供選擇,Spinner 用它的內(nèi)容創(chuàng)建了一個(gè) ?ListView
?昼窗。該 ListView
可能會(huì)很長(zhǎng)是趴,而且它的元素不會(huì)出現(xiàn)在視圖結(jié)構(gòu)中。通過(guò)使用 ?onData()
? 我們強(qiáng)制將想要得到的元素加入到視圖結(jié)構(gòu)中澄惊。Spinner 中的元素是字符串唆途,我們想要匹配的條目是字符串類(lèi)型并且值是 “Americano”。
onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());
3. 驗(yàn)證 TextView
? 包含 “Americano” 字符串
onView(withId(R.id.spinnertext_simple).check(matches(withText(containsString("Americano"))));
調(diào)試
當(dāng)測(cè)試失敗時(shí)掸驱,Espresso 會(huì)提供有用的調(diào)試信息:
日志
Espresso 將所有視圖操作記錄到 logcat 中肛搬。例如:
ViewInteraction: Performing ‘single click’ action on view with text: Espresso
視圖結(jié)構(gòu)
當(dāng) onView()
? 執(zhí)行失敗時(shí),Espresso 會(huì)在異常字符串里打印視圖結(jié)構(gòu)毕贼。
- 如果
?onView
? 沒(méi)有找到目標(biāo)視圖温赔,會(huì)拋出?NoMatchingViewException
?。你可以檢查異常字符串中的視圖結(jié)構(gòu)來(lái)分析為什么匹配器沒(méi)有匹配到視圖帅刀。 - 如果
?onView()
? 根據(jù)給出的匹配器找到了多個(gè)視圖让腹,會(huì)拋出?AmbiguousViewMatcherException
?远剩。視圖結(jié)構(gòu)會(huì)被打印出來(lái),并且所有被匹配的視圖都會(huì)帶有 MATCHES 標(biāo)簽:
java.lang.RuntimeException:
com.google.android.apps.common.testing.ui.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****
當(dāng)處理一個(gè)完整的視圖結(jié)構(gòu)或控件異常行為時(shí)骇窍,使用 Android 視圖結(jié)構(gòu)查看器有利于你給出說(shuō)明瓜晤。
?AdapterView
? 提醒
Espresso 會(huì)提醒用戶 AdapterView
控件的出現(xiàn)。當(dāng) ?onView
? 操作拋出 ?NoMatchingViewException
? 異常而且 ?AdapterView
? 控件在視圖結(jié)構(gòu)中時(shí)腹纳,最常見(jiàn)的解決方法是使用 onData()
痢掠。異常信息中將會(huì)包含一個(gè)帶有一列適配器視圖的提醒。你可以通過(guò)此信息來(lái)調(diào)用 onData 加載目標(biāo)視圖嘲恍。