本文由
玉剛說寫做平臺
提供寫做贊助html原做者:
卻把青梅嗅
android版權聲明:本文版權歸微信公衆號
玉剛說
全部,未經許可,不得以任何形式轉載git
接下來我將說到這種狀況並不是個例——做爲一個Android開發者,當我實現了一個界面的一些功能,或者對界面上某些功能進行了修改,我該如何去查收我想要的結果呢?github
最簡單的方式就是直接編譯運行App,經過本身的操做對界面進行交互,從我的的視覺效果上進行功能的檢查,好比我實現了一個RecyclerView,我就打開界面,看看這個列表是否正確顯示在了界面上。緩存
不久以後,我以爲某些地方代碼不是很好,因而我改了一些代碼,我怕會出現問題,因而爲了保證項目可以不出問題(至少是避免低級的錯誤),我選擇再次編譯運行,驗收結果。bash
再深刻一點,若是每一個版本發佈前都須要這麼屢次測試,或者每當咱們簡單修改了一下代碼,就須要更屢次重複進行以上步驟,並檢測結果,來來每每,反反覆覆,實在使人乏味。微信
也許, UI自動化測試是一勞永逸解決這個問題的方案之一。網絡
充滿熱情,一腔熱血,說學就學,我行我上。架構
相信我,不要這樣,這和學習庫或者框架不同,UI自動化測試是一個專業技能。不信的話,請參考一下各大機構對於測試工程師的培訓週期,系統性走一遍全日制要幾個月,閒暇時間學習?學不完的,並且,不必。app
以Android官方文檔的概述,AndroidStudio提供了幾種UI測試工具供開發者使用。
事實上UI的自動化測試工具不少,但對於Android開發者來說,掌握其中的1至2項,就足以在UI測試領域立足,本文僅簡單介紹基礎的幾款工具以拋磚引玉。
Monkey是Android SDK自帶的測試工具,在測試過程當中會向系統發送僞隨機的用戶事件流,如按鍵輸入、觸摸屏輸入、手勢輸入等),實現對正在開發的應用程序進行壓力測試,也有日誌輸出。實際上該工具只能作程序作一些壓力測試,因爲測試事件和數據都是隨機的,不能自定義,因此有很大的侷限性。
Instrumentation是早期Google提供的Android自動化測試工具類,雖然在那時候JUnit也能夠對Android進行測試,可是Instrumentation容許你對應用程序作更爲複雜的測試,甚至是框架層面的。經過Instrumentation你能夠模擬按鍵按下、擡起、屏幕點擊、滾動等事件。Instrumentation是經過將主程序和測試程序運行在同一個進程來實現這些功能,你能夠把Instrumentation當作一個相似Activity或者Service而且不帶界面的組件,在程序運行期間監控你的主程序。缺點是對測試人員來講編寫代碼能力要求較高,須要對Android相關知識有必定了解,還須要配置AndroidManifest.xml文件,不能跨多個App。
UiAutomator也是Android提供的自動化測試框架,基本上支持全部的Android事件操做,對比Instrumentation它不須要測試人員瞭解代碼實現細節(能夠用UiAutomatorviewer抓去App頁面上的控件屬性而不看源碼)。基於Java,測試代碼結構簡單、編寫容易、學習成本,一次編譯,全部設備或模擬器都能運行測試,能跨App(好比:不少App有選擇相冊、打開相機拍照,這就是跨App測試)。缺點是隻支持SDK 16(Android 4.1)及以上,不支持Hybird App、WebApp。
Espresso是Google的開源自動化測試框架。相對於Robotium和UIAutomator,它的特色是規模更小、更簡潔,API更加精確,編寫測試代碼簡單,容易快速上手。由於是基於Instrumentation的,因此不能跨App。
以上這些工具的概述,節選引用自知乎:Android 手機自動化測試工具備哪幾種?
UI的自動化測試的是一個複雜的系統,所謂望山跑死馬,做爲Android開發者,咱們想要經過閒暇的時間,指望短時間可以精通UI自動化測試是不現實的,可是每次都運行app手動測試又顯得很蠢,最好的方式,是經過了解並學習一個經典的UI測試工具,在瞭解到UI自動化測試的好處以後,再選擇繼續深刻仍是功成身退。
有心的同窗已經注意到了,上文中最後介紹的那個Espresso怎麼這麼眼熟呢?確實如此,在AndroidStudio2.2版本以後,在新建的項目中,AndroidStudio會默認添加Espresso的依賴。
這樣看來,Espresso顯然是一個不錯的選擇。正如Google所但願的,當Android的開發者利用Espresso寫完測試用例後,能一邊看着測試用例自動執行,一邊享受一杯香醇的Espresso(意式咖啡)。
Google官方但願咱們經過Espresso減小重複的勞動,那麼這所謂的UI自動化測試效果如何呢,正所謂手下見真章,咱們來看一下Google的todo App的測試代碼運行時的效果:
Espresso的原理是,經過測試代碼模擬用戶對UI元素的操做,以後再校驗(verify)操做後的結果,和咱們人爲操做不一樣,Espresso可以在短期內測試全部的case,正如你所見的同樣。
咱們不由這樣想,若是一個界面涉及到不少的操做,沒有Espresso測試代碼以前,每次修改,工做的責任感須要讓我本身先跑一遍全部功能,而後纔敢打包扔給QA,可是若是我寫好了自動化測試代碼,是否是意味着每次改完代碼,只需跑一遍測試代碼就代替了以前的手工操做呢?
如您所見,本次測試了一個界面19個不一樣的操做,整個自動化過程共花費了4m34s,但在這個過程當中我能夠衝一杯咖啡,或者看看技術博客,甚至是發呆——我愜意地獲得了期冀的結果。
若是我負責的功能模塊全部界面,都覆蓋了這樣的測試代碼,多好啊......——若是屏幕面前的您,有學習藉助自動化測試工具偷懶的想法,請堅持閱讀下去,以一個開發人員而非專業測試人員的視角,分享學習自動化測試的經驗,這也正是本文的目的。
本文的目標是,以本身的學習經歷爲基礎,爲想要學習Espresso(或者有這個想法)的同窗,提供一個系統性的規劃和建議。
這意味着,本文不會去詳細闡述每一個API的使用,於我而言,這些應該交給官方文檔去闡釋,固然,對於API而言,我也不認爲可以講述的比官方文檔更優秀。
我會經過一些簡單的測試代碼闡述UI自動化測試所須要的一些基礎或思想,可是代碼自己不該該是本文的重點,我更但願,當您讀完本文,您能有啊,原來Espresso的UI測試應該這麼學之感——而不是哦,原來這個API是這麼用的。
若是將本文的定義類比於該知識體系的目錄或者導航,我以爲再恰當不過。
個人建議是按照如下步驟進行學習:
1. Fork Google官方的Demo代碼,運行並感覺測試代碼的威力
Google官方的todo案例地址:
https://github.com/googlesamples/android-architecture
咱們拉下來代碼後,選一個您比較感興趣的分支,好比比較簡單一點的todo-mvp分支,這個分支中代碼的實現僅僅使用了MVP的架構,學習起來並不複雜。
咱們來看一下項目的目錄結構:
其中androidTest和androidTestMock都是UI測試的代碼,咱們先右鍵點擊androidTest文件夾,run該文件夾下的全部UI測試case。
選中設備後,AndroidStudio就會編譯並自動打包(注意實際上此處的測試打包和實際生產的打包並不同),而後自動在設備上運行全部的測試case——就和上文中的效果同樣。
看到這裏,咱們不只感嘆測試代碼的強大,不要沉迷於此,咱們繼續第二步:
2. 閱讀Espresso的官方文檔
若是點進去看測試代碼的話,咱們會比較懵逼,由於咱們對於Espresso的使用一無所知,那麼接下來咱們要去作的,就是閱讀Espresso的官方文檔了:
https://developer.android.com/training/testing/espresso/basics
https://lovexiaov.gitbooks.io/official-espresso-doc/content/
中文翻譯的gitbook的確很差找,在此不只感嘆UI自動化測試的小衆性,特別感謝譯者lovexiaov,沒有你的分享精神,我就只能考慮本身去硬啃英文文檔了。
實話說,中文的文檔部分翻譯不夠準確,建議你們,有能力仍是看英文原版,我更建議你們中英文對照學習。
這一步,咱們不須要深刻學習並使用文檔中列舉的全部API,只須要參照文檔看得懂todoApp中測試代碼的用意就好了。
3. 付諸實踐
當咱們參照API文檔,而且可以基本看得懂demo代碼中,大部分測試case想要幹什麼,咱們接下來就能夠嘗試付諸實踐了。
接下來我將會用簡單的代碼闡述Espresso的簡單使用。
來一個最簡單的demo,當咱們點擊一個Button,讓界面某個TextView顯示HelloEspresso的文字內容。
咱們忽略xml佈局的實現,簡單看一下Activity中的部分Java代碼:
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.button:
// 點擊button後,textview顯示hello espresso!
textView.setVisibility(View.VISIBLE);
textView.setText("hello espresso!");
break;
}
}
複製代碼
很是簡單,測試代碼天然也淺顯易懂:
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
@Rule
public ActivityTestRule<MainActivity> rule =
new ActivityTestRule<>(MainActivity.class);
@Test
public void clickTest() {
//檢驗:一開始,textView不顯示
onView(withId(R.id.textView))
.check(matches(not(isDisplayed())));
//檢驗:button的文字內容
onView(withId(R.id.button))
.check(matches(withText("修改內容")))
.perform(click()); //操做:點擊按鈕
//檢驗:textView內容是否修改,而且變爲可見
onView(withId(R.id.textView))
.check(matches(withText("hello espresso!")))
.check(matches(isDisplayed()));
}
}
複製代碼
代碼很是簡單,邏輯也很清晰,咱們測試的思路是,找到咱們要操做的界面元素,而後操做該界面元素,而後校驗UI的變化。
在這個測試中,當咱們點擊了button後,會校驗:界面上TextView變爲可見,同時附有「hello espresso!」的內容——若是測試失敗了,說明咱們預期的操做並未獲得預期的結果,咱們就須要去檢查代碼了。
接下來咱們跳轉另一個場景,稍微複雜一點,界面上有一個EditText,負責用戶輸入帳號,還有一個Button,負責登陸,還有一個TextView,當用戶點擊Button後,TextView會顯示登陸成功而且清空輸入框:
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.button2:
// 登錄成功而且清空輸入框
textView.setVisibility(View.VISIBLE);
textView.setText("登陸成功");
editText.setText("");
break;
}
}
複製代碼
咱們能夠這樣補充測試Case:
@Test
public void loginTest() throws Exception {
//先清除editText的內容,而後輸入,而後關閉軟鍵盤,最後校驗內容
//這裏若是要輸入中文,使用replaceText()方法代替typeText()
onView(withId(R.id.editText))
.perform(
clearText(),
replaceText("username"),
closeSoftKeyboard()
)
.check(matches(withText("username")));
// 操做:點擊Button
onView(withId(R.id.button2))
.perform(click());
//校驗:textView的內容和可見
onView(withId(R.id.textView))
.check(matches(withText("登陸成功")))
.check(matches(isDisplayed()));
//校驗:editText的文字內容(被清空)和hint
onView(withId(R.id.editText))
.check(matches(withText("")))
.check(matches(withHint("請輸入帳戶名")));
}
複製代碼
大功告成——和基本案例基本差很少,都是經過簡單的對View的操做+校驗完成了UI的測試代碼編寫。
看起來咱們已經熟悉了Espresso的使用套路,我已經有信心在真實的項目中應用它了。
正所謂行百里路半九十,當咱們將看起來並不複雜的Espresso應用在真實的項目中時,咱們立刻就會遇到一個很嚴重的問題,那就是:
並不是全部的UI操做都是同步響應的!
Espresso進行一個簡單的同步功能測試並不難,好比咱們點擊了一個Button,點擊後改變對應某個TextView的內容,這很簡單。但實際正常開發中,這種簡單的邏輯測試是不多見的,相反,咱們須要測試的是各類各樣的異步測試,好比:
情景一:點擊進入Activity,網絡請求數據加載,成功後數據展現在界面上。 情景二:點擊進入Activity,得到緩存,網絡請求數據加載,成功後數據展現在界面上,處理緩存。 情景N : ......
假設這樣一個簡單的網絡請求測試:
@Test
public void testHttp() {
// 咱們請求網絡數據,成功後讓TextView顯示"網絡請求成功"
// 同時ImageView從不可見變爲可見
//若是咱們直接檢查是否是請求到了數據
onView(withId(R.id.textView)).check(matches(withText("網絡請求成功!")));
onView(withText(R.id.imageView)).check(matches(isDisplayed()));
}
複製代碼
若是咱們直接測試,那麼很大機率會報錯,由於在咱們要測試數據是否展現在UI上時,網絡數據頗有可能尚未獲取到。
這很難處理,由於咱們不知道數據到底何時才能獲取到,有同窗抖了個機靈,說咱們能夠這樣:
@Test
public void testHttp() {
// 咱們一進來就先讓他等待5秒,等數據加載完畢再檢查UI
Thread.sleep(5000);
// 5秒結束,咱們檢查是否是請求到了數據
onView(withId(R.id.textView)).check(matches(withText("網絡請求成功!")));
onView(withText(R.id.imageView)).check(matches(isDisplayed()));
}
複製代碼
這樣能夠實現嗎,這個大機率真的能夠,可是這種測試顯然問題不少,由於網絡狀況是在不斷變化的,也許0.5s就能獲取網絡數據,也有可能數十秒後才能獲取,這樣前者致使咱們浪費了4.5s的時間,後者在網絡狀態屬於正常的時候測試結果失敗,這都是咱們不肯看到的結果。
咱們更但願在獲取到網絡數據以後,當即進行下一步的測試,所以咱們須要對網絡數據的獲取狀況進行監聽。
可是問題來了,如何在UI測試代碼中,對真實的網絡狀態進行監聽呢?
這個問題難倒了我,好在Google的工程師們已經在todo的demo中提供了一種解決的方式,咱們來看一看官方的方案。
Google官方提供了IdlingResource以供開發者進行UI的異步測試,對於IdlingResource的解釋說明,咱們能夠參照官方文檔:
https://developer.android.com/training/testing/espresso/idling-resource
或者中文文檔對於IdlingResource的解釋:
https://lovexiaov.gitbooks.io/official-espresso-doc/content/chapter6.html
我不會用大段代碼闡述如何使用,它的基本原理是:在生產代碼中,定義一個Flag(標記),當開始異步請求前,修改Flag的狀態,當網絡請求結束後,將Flag的狀態重置,這時候Flag狀態修改的事件會被髮送通知給註冊的對象(好比測試代碼):
@Before
public void setUp() throws Exception {
idlingresource = activityRule.getActivity().getIdlingresource();
}
@Test
public void onLoadingFinished() throws Exception {
// 再也不須要這樣的代碼
// Thread.sleep(5000);
// 註冊異步監聽,此時測試會被掛起
// 當網絡請求結束後,生產代碼中Flag狀態的改變,會繼續執行測試代碼
Espresso.registerIdlingResources(idlingresource);
// 繼續執行代碼
onView(withId(R.id.text))
.check(matches(withText("success!")));
}
@After
public void release() throws Exception {
// 固然,咱們須要在測試結束後取消註冊,釋放資源
Espresso.unregisterIdlingResources(idlingresource);
}
複製代碼
這種行爲的好處不言而喻,它可以在異步結束以後立刻執行接下來的測試代碼,從效果上來講,相比Thread.sleep(5000);
不知好了多少。
它的缺點也很明顯,那就是測試代碼實際須要依賴對生產代碼進行配置(本文中並未展現,請參考todoDemo或者這篇文章)。
難道就沒有更好的解決方案嗎?固然有,Google對此的建議是,重構項目代碼(好比增長product flavors,或者經過依賴注入等等),使其變得可測試性——到這一步,請慎重考慮,由於這已經涉及到項目的架構以及項目管理的層級上了。
實際上,Espresso在應用在實際項目中,須要咱們去面對的問題並很多,絕大多數狀況下,這些問題都可以經過搜索引擎而後親自實踐去解決—— 你毫不是一我的在戰鬥。
不少同窗都瞭解單元測試和UI自動化測試的重要性,可是這些工具須要不菲的時間成本,那麼它們真的還有必要去學習嗎?
有位同窗舉手了,他一樣表示——有時雖然修改一個小功能,須要開發者屢次手動測試很麻煩,可是也並不是不可接受,至少上班時,在項目編譯運行期間,我能夠切換網頁,看看新聞摸摸魚。
固然還有這樣的狀況,做爲一個開發人員,即便我學會了自動化測試,我也不必定有機會去應用它,直接嘗試應用在一個已經完善的成熟項目中,是不現實的;這樣的話,會不會出現,學會了但根本用不到的窘境?
每一個人都不能保證未來仍是否還會遇到曾經的問題,若是遇到了,我該怎麼作,是選擇繼續躲避,仍是一勞永逸;並且,即便學會了,如何保證這能成爲我核心競爭力的一部分,而不是學會了卻用不到,最終被慢慢忘記?
的確,咱們有時的確沒有必要爲工做作出額外的付出(思考和實踐),萬一搞砸了,反而不如不作。可是我要闡述的一點是:不作並不意味着問題被解決了——你只是暫時避開了它,而下一次遇到它的時候,你仍需去面對這個困境;而且,若是將測試任務交給了代碼,摸魚的時候豈不更加輕鬆?正所謂,授人以漁,勞神費力。 而——
言歸正傳,對於如何實踐自動化測試,個人方式是對我的的一些工具代碼進行UI自動化測試的覆蓋,在進一步完善本身的工具同時,深刻了解Espresso。
筆者對於Espresso的經驗所得來自於本身的**Github的這個工具,它是Android的一個響應式圖片選擇器**,所以每次發佈新版本筆者都須要本身測試UI,而UI自動化測試無疑能夠減小這些重複的操做。 ——這個庫UI測試的更詳細過程並不是本文的重點,我在另外一篇文章中去闡述了它:
全副武裝!AndroidUI自動化測試在RxImagePicker中的實踐歷程
一千個觀衆眼中有一千個哈姆雷特,只要感興趣,總能找到適合本身的方式,本文所講述的Espresso僅僅是UI自動化測試這門專業技術的一部分,但我認爲它很契合Android開發者,並藉助它爲本身的UI界面進行白盒測試(也有朋友稱Espresso爲灰盒測試),正如官方文檔所描述的(下爲譯文):
Espresso 的使用羣體爲堅信自動化測試是開發週期中必不可少的一部分的開發者。雖然它可被用來作黑盒測試,但 Espresso 會在對被測代碼庫熟悉的人手中火力全開。
在咱們感嘆AndroidStudio默認提供的依賴庫中,JUnit4可讓咱們經過單元測試保證最小模塊代碼的可靠性,ConstraintLayout讓咱們減小大量佈局嵌套的同時慢慢拋棄了RelativeLayout的同時,請也不要忽視Espresso,真正瞭解了它並付諸實踐,便會對它強大的UI自動化測試功能愛不釋手。