本文為『移動(dòng)前線』群在4月23日的分享總結(jié)整理而成,轉(zhuǎn)載請(qǐng)注明來(lái)自『移動(dòng)開(kāi)發(fā)前線』公眾號(hào)。
大家好,我是蘑菇街支付金融部門(mén)的鄒勇,花名叫小創(chuàng)。今天很高興跟大家分享一下安卓的單元測(cè)試在蘑菇街支付金融的實(shí)踐。下面,我們從為什么開(kāi)始。 為什么要寫(xiě)單元測(cè)試首先要介紹為什么蘑菇街支付金融這邊會(huì)采用單元測(cè)試的實(shí)踐。說(shuō)起來(lái)比較巧,剛開(kāi)始的時(shí)候,只是我一個(gè)人會(huì)寫(xiě)單元測(cè)試。后來(lái)老板們知道了,覺(jué)得這是件很有價(jià)值的事情,于是就叫我負(fù)責(zé)我們組的單元測(cè)試這件事情。就這樣慢慢的,單元測(cè)試這件事情就成了我們這邊的正常實(shí)踐了。再后來(lái),在公司層面也開(kāi)始有一定的推廣。 要說(shuō)為什么要寫(xiě)單元測(cè)試的話,我相信大部分人都能承認(rèn)、也能理解單元測(cè)試在保證代碼質(zhì)量,防止bug或盡早發(fā)現(xiàn)bug這方面的作用,這可能是大家覺(jué)得單元測(cè)試最大的作用。然而我覺(jué)得,除了這方面的作用,單元測(cè)試還能在很大程度上改善代碼的設(shè)計(jì),同時(shí)還能節(jié)約時(shí)間,讓人工作起來(lái)更自信、更開(kāi)心,以及其他的一些好處。這些都是我的切身感受,我相信也是多數(shù)真正實(shí)踐過(guò)單元測(cè)試的人的切身感受,而不是為了宣傳這個(gè)東西而說(shuō)的好聽(tīng)的大話。 說(shuō)到節(jié)約時(shí)間,大家可能就會(huì)好奇了,寫(xiě)單元測(cè)試需要時(shí)間,維護(hù)單元測(cè)試代碼也需要時(shí)間,應(yīng)該更費(fèi)時(shí)間才對(duì)??? 這就是在開(kāi)始分享之前,我想重點(diǎn)澄清的一點(diǎn),那就是,單元測(cè)試本身其實(shí)不會(huì)占用多少時(shí)間,相反,還會(huì)節(jié)約時(shí)間。只是:
打個(gè)比方,開(kāi)車(chē)這件事情,需要很多時(shí)間嗎?我相信很少人會(huì)說(shuō)開(kāi)車(chē)這件事情需要很多時(shí)間,而是:
那為什么說(shuō)單元測(cè)試可以節(jié)約時(shí)間呢?簡(jiǎn)單說(shuō)幾點(diǎn):
所以,我希望大家能去掉"沒(méi)時(shí)間寫(xiě)單元測(cè)試"這個(gè)印象,如果工作上安排太緊,沒(méi)有時(shí)間學(xué)習(xí)如何做單元測(cè)試的話,可以自己私底下學(xué),然后在慢慢應(yīng)用到項(xiàng)目中。 單元測(cè)試簡(jiǎn)單介紹接下來(lái)介紹我們這邊是怎么做安卓單元測(cè)試的。首先澄清一下概念,在安卓上面寫(xiě)測(cè)試,有很多技術(shù)方案。有JUnit、Instrumentation test、Espresso、UiAutomator等等,還有第三方的Appium、Robotium、Calabash等等。我們現(xiàn)在講的是使用JUnit和其他的一些框架,寫(xiě)可以在我們開(kāi)發(fā)環(huán)境的JVM上面直接運(yùn)行的單元測(cè)試,其他的幾種其實(shí)都不屬于單元測(cè)試,而是集成測(cè)試或者叫Functional test等等。這兩者明顯的不同是,前者可以直接在開(kāi)發(fā)用的電腦,或者是CI上面的JVM上運(yùn)行,而且可以只運(yùn)行那么一小部分代碼,速度非常快。而后者必須要有模擬器或真機(jī),把整個(gè)project打包成一個(gè)app,然后上傳到模擬器或真機(jī)上,再運(yùn)行相關(guān)的代碼,速度相對(duì)來(lái)說(shuō)慢很多。 單元測(cè)試的定義相信大家都知道,就是為我們寫(xiě)的某一個(gè)代碼單元(比如一個(gè)方法)寫(xiě)的測(cè)試代碼。一個(gè)單元測(cè)試大概可以分為三個(gè)部分:
然而一個(gè)類的方法分兩種,一種是有返回值的方法。一種是沒(méi)有返回值的方法,即void方法。對(duì)于有返回值的方法,固然測(cè)試起來(lái)是很容易的,但是對(duì)于沒(méi)有返回值的方法,該怎么測(cè)試呢?這里的關(guān)鍵是,怎么樣獲取這個(gè)方法的“返回結(jié)果”? 這里舉一個(gè)例子來(lái)說(shuō)明一下,順便澄清一個(gè)十分常見(jiàn)的誤解。比如說(shuō)有一個(gè)Activity,管他叫DataActivity,它有一個(gè)public void loadData()方法, 會(huì)去調(diào)用底層的DataModel類,異步的執(zhí)行一些網(wǎng)絡(luò)請(qǐng)求。當(dāng)網(wǎng)絡(luò)請(qǐng)求返回以后,更新用戶界面。 這里的loadData()方法是void的,它該怎么測(cè)試呢?一個(gè)最直接的反應(yīng)可能是,調(diào)用loadData()方法(當(dāng)然,實(shí)際可能是通過(guò)其他事件觸發(fā)),然后一段時(shí)間后,驗(yàn)證界面得到了更新。然而這種方法是錯(cuò)的,這種測(cè)試叫集成測(cè)試,而不是單元測(cè)試。因?yàn)樗婕暗胶芏鄠€(gè)方面,它涉及到DataModel、網(wǎng)絡(luò)服務(wù)器,以及網(wǎng)絡(luò)返回正確時(shí),DataActivity內(nèi)部的處理,等等。集成測(cè)試固然有它的必要性,但不是我們應(yīng)該最關(guān)注的地方,也不是最有價(jià)值的地方。我們應(yīng)該最關(guān)注的是單元測(cè)試。關(guān)于這一點(diǎn),有一個(gè)Test Pyramid的理論:
Test Pyramid理論基本大意是,單元測(cè)試是基礎(chǔ),是我們應(yīng)該花絕大多數(shù)時(shí)間去寫(xiě)的部分,而集成測(cè)試等應(yīng)該是冰山上面能看見(jiàn)的那一小部分。 那么對(duì)于這個(gè)case,正確的單元測(cè)試方法,應(yīng)該是去驗(yàn)證loadData()方法調(diào)用了DataModel的某個(gè)請(qǐng)求數(shù)據(jù)的方法,同時(shí)傳遞的參數(shù)是正確的。“調(diào)用了DataModel的方法,同時(shí)參數(shù)是。。?!?這個(gè)才是loadData()這個(gè)方法的“返回結(jié)果”。 Mock的概念以及Mockito框架要驗(yàn)證某個(gè)對(duì)象的某個(gè)方法得到調(diào)用了,就涉及到mock的使用。這里對(duì)mock的概念做個(gè)簡(jiǎn)單介紹,以免很多同學(xué)不熟悉,mock就是創(chuàng)建一個(gè)虛假的、模擬的對(duì)象。在測(cè)試環(huán)境下,用來(lái)替換掉真實(shí)的對(duì)象。這樣就能達(dá)到兩個(gè)目的:
要使用mock,一般需要使用mock框架,目前安卓最常用的有兩個(gè),Mockito和JMockit。兩者的區(qū)別是,前者不能mock static method和final class、final method,后者可以。我們依然采用的是Mockito,原因說(shuō)起來(lái)慚愧,是因?yàn)閯傞_(kāi)始并不知道JMockit這個(gè)東西,后來(lái)查了一些資料,看過(guò)很多對(duì)比Mockito和JMockit的文章,貌似大部分還是很看好JMockit的,只是有一個(gè)問(wèn)題,那就是跟robolectric的結(jié)合也有一些bug,同時(shí)使用姿勢(shì)跟Mockito有較大的不同,因此一直沒(méi)有抽時(shí)間去實(shí)踐過(guò)。這個(gè)希望以后能夠做進(jìn)一步的調(diào)查,到時(shí)候在給大家分享一下使用感受。 但是使用Mockito,就有一個(gè)問(wèn)題,那就是static method和final class、final method沒(méi)有辦法mock,對(duì)于這點(diǎn)如何解決,我們稍后會(huì)介紹到。 在測(cè)試環(huán)境中使用mock:依賴注入接下來(lái)的一個(gè)問(wèn)題就是,如何在測(cè)試環(huán)境下,把DataModel換成mock的對(duì)象,而正式代碼中,DataModel又是正常的對(duì)象呢? 這個(gè)問(wèn)題也有兩種解決方案,一是使用專門(mén)的testing product flavor;二是使用依賴注入。第一種方案就是用一個(gè)專門(mén)的product flavor來(lái)做testing,在這個(gè)testing flavor里面,里面把需要mock的類寫(xiě)一份mock的implementation,然后通過(guò)factory提供給client,這個(gè)factory的接口在testing flavor和正式的flavor里面是一樣的,在跑testing的時(shí)候,專門(mén)使用這個(gè)testing flavor,這樣通過(guò)factory得到的就是mock的類。這種情況看起來(lái)很簡(jiǎn)單,但其實(shí)很不靈活,因?yàn)橹挥幸环Nmock實(shí)現(xiàn);此外,代碼會(huì)變得很丑陋,因?yàn)槟阈枰獮槊恳粋€(gè)dependency提供一個(gè)factory,會(huì)覺(jué)得很刻意;再者,多了一個(gè)flavor,很多gradle任務(wù)都會(huì)變得很慢。關(guān)于這種方案,可以參考這個(gè)視頻(https://www./watch?v=vdasFFfXKOY)。 因此,我們用的是第二種,依賴注入。先簡(jiǎn)單介紹一下依賴注入這個(gè)模式,他的基本理念是,某一個(gè)類(比如說(shuō)DataActivity),用到的內(nèi)部對(duì)象(比如說(shuō)DataModel)的創(chuàng)建過(guò)程不在DataActivity內(nèi)部去new,而是由外部去創(chuàng)建好DataModel的實(shí)例,然后通過(guò)某種方式set給DataActivity。這種模式應(yīng)用是非常廣泛的,尤其是在測(cè)試的時(shí)候。為了更方便的做依賴注入,如今有很多框架專門(mén)做這件事情,比如RoboGuice、Dagger、Dagger2等等。我們用的是Dagger2,理由很簡(jiǎn)單,這是目前最好用的DI框架。 關(guān)于Dagger2的文章,之前我們?nèi)豪镆卜窒砹瞬簧?,但是好像我并沒(méi)有看到講述沒(méi)有關(guān)于如何在測(cè)試環(huán)境下使用Dagger2的文章,這個(gè)還是略感遺憾的。離開(kāi)單元測(cè)試,使用依賴注入就少了很有說(shuō)服力的一個(gè)理由。 那么這里我就介紹一下,怎么樣把Dagger2應(yīng)用到單元測(cè)試中。熟悉dagger2的童靴可能知道,Dagger2里面最關(guān)鍵的有兩個(gè)概念,Module和Component。Module是負(fù)責(zé)生成諸如DataModel這樣被別人(比如DataActivity)使用的類的地方。用術(shù)語(yǔ)的話,被別人使用的類DataModel叫Dependency,使用到了別的類的類DataActivity叫Client。而Component則是供Client使用Dependency的統(tǒng)一接口。也就是說(shuō),DataActivity通過(guò)Component,來(lái)得到一份DataModel的實(shí)例。 現(xiàn)在,關(guān)鍵的地方來(lái)了,Component本身是不生產(chǎn)dependency的,它只是搬運(yùn)工而已,真正生產(chǎn)dependency的地方在Module。所以,創(chuàng)建Component需要用到Module,不同的Module生產(chǎn)出不同的dependency。在正式代碼里面,我們使用正常的Module,生產(chǎn)正常的DataModel。而在測(cè)試環(huán)境中,我們寫(xiě)一個(gè)TestingModule,讓它繼承正常的Module,然后override掉生產(chǎn)DataModel的方法,讓它生產(chǎn)mock的DataModel。在跑單元測(cè)試的時(shí)候,使用這個(gè)TestingModule來(lái)創(chuàng)建Component,這樣的話,DataActivity通過(guò)Component得到的DataModel對(duì)象就是mock出來(lái)的DataModel對(duì)象。 使用這種方式,所有production code都不用專門(mén)為testing增加任何多余的代碼,同時(shí)還能得到依賴注入的其他好處。 Robolectric:解決Android單元測(cè)試最大的痛點(diǎn)接下來(lái)講講Android單元測(cè)試最大的痛點(diǎn),那就是JVM上面運(yùn)行純JUnit單元測(cè)試時(shí)是不能使用Android相關(guān)的類的,因?yàn)槲覀冮_(kāi)發(fā)用到的安卓環(huán)境是沒(méi)有實(shí)現(xiàn)的,里面只定義了一些接口,所有方法的實(shí)現(xiàn)都是throw new RuntimeException("stub");,如果我們單元測(cè)試代碼里面用到了安卓相關(guān)的代碼的話,那么運(yùn)行時(shí)就會(huì)遇到RuntimeException("Stub")。 要解決這個(gè)問(wèn)題,一般來(lái)說(shuō)有三種方案:
第一種方案能work,但是速度非常慢,因?yàn)槊看芜\(yùn)行一次單元測(cè)試,都需要將整個(gè)項(xiàng)目打包成apk,上傳到模擬器或真機(jī)上,就跟運(yùn)行了一次app似得,這個(gè)顯然不是單元測(cè)試該有的速度,更無(wú)法做TDD。這種方案首先被否決。 剛開(kāi)始,我們采用的是Robolectric,原因有兩個(gè):
然而慢慢的,我們的態(tài)度從擁抱Robolectric,到盡量不用它,盡量使用純java代碼去實(shí)現(xiàn)??赡艽蠹矣X(jué)得安卓相關(guān)的代碼會(huì)很多,而純java的很少,然而慢慢的你會(huì)發(fā)現(xiàn),其實(shí)不是這樣的,純java的代碼其實(shí)真不少,而且往往是核心的邏輯所在。之所以盡量不用Robolectric,是因?yàn)镽obolectric雖然相對(duì)于Instrumentation testing來(lái)說(shuō)快多了。但畢竟他也需要merge一些資源,build出來(lái)一個(gè)模擬的app,因此相對(duì)于純java和JUnit來(lái)說(shuō),這個(gè)速度依然是很慢的。 用具體的數(shù)字來(lái)對(duì)比說(shuō)明:
當(dāng)然,雖然運(yùn)行一次Robolectric在10秒左右,但是對(duì)比運(yùn)行一次app,還是要快太多。因此,剛開(kāi)始的時(shí)候,從Robolectric開(kāi)始完全是OK的。 以上就是現(xiàn)在我們這邊單元測(cè)試用到的幾個(gè)基本技術(shù):JUnit4 + Mockito + Dagger2 + Robolectric?;緛?lái)說(shuō),并沒(méi)有什么黑科技,都是業(yè)界標(biāo)準(zhǔn)。 一個(gè)具體的案例接下來(lái),我通過(guò)一個(gè)具體的案例,跟大家介紹一下,我們這邊的一個(gè)app,具體是怎么單測(cè)的。 這里是我們收銀臺(tái)界面的樣子:
假設(shè)Activity名字為CheckoutActivity,當(dāng)它啟動(dòng)的時(shí)候,CheckoutActivity會(huì)去調(diào)一個(gè)CheckoutModel的loadCheckoutData()方法,這個(gè)方法又會(huì)去調(diào)更底層的一個(gè)封裝了用戶認(rèn)證等信息的網(wǎng)絡(luò)請(qǐng)求Api類(mApi)的get方法,同時(shí)傳給這個(gè)Api類一個(gè)callback。這個(gè)callback的做的事情是將結(jié)果通過(guò)Otto Bus(mBus) post出去。CheckoutActivity里面Subscribe了這個(gè)Event(方法名是onCheckoutDataLoaded()),然后根據(jù)Event的值相應(yīng)的顯示數(shù)據(jù)或錯(cuò)誤信息。 代碼簡(jiǎn)寫(xiě)如下:
這里,CheckoutActivity里面的mCheckoutModel、CheckoutModel里面的mApi、CheckoutModel里面的mBus,都是通過(guò)Dagger2注入進(jìn)去的。在做單元測(cè)試的時(shí)候,這些都是mock。 對(duì)于這個(gè)流程,我們做了如下的單元測(cè)試:
這里需要說(shuō)明的一點(diǎn)是,上面的每一個(gè)測(cè)試,都是獨(dú)立進(jìn)行的,不是說(shuō)下面的單元測(cè)試依賴于上面的?;蛘哒f(shuō)必須先做上面的,再做下面的。 這部分較為詳細(xì)的代碼放在github(https://github.com/ChrisZou/android-unit-testing-tutorial)上,groupshare這個(gè)package里面。 其他的問(wèn)題以上就是我們這邊做單元測(cè)試用到的技術(shù),以及一個(gè)基本流程,下面聊聊其他的幾個(gè)問(wèn)題。 哪些東西需要測(cè)試呢?
CI和code coverage: Jacoco要把單元測(cè)試正式化,CI是非常重要的一步,我們有一個(gè)運(yùn)行Jenkins的CI server,每次開(kāi)發(fā)者push代碼到master branch的時(shí)候,會(huì)運(yùn)行一次單元測(cè)試的gradle task,同時(shí)使用Jacoco做code coverage。 這里有個(gè)坑要特別注意,那就是項(xiàng)目里面的gradle Jacoco插件和Jenkins的Jacoco插件的兼容性問(wèn)題。我們用的gradle Jacoco插件是7.1,更高版本的好像有問(wèn)題。然后對(duì)應(yīng)的Jenkins的Jacoco插件需要1.0.19或更低版本的,更高版本的jenkins plugin不支持低版本的gradle Jacoco項(xiàng)目版本。實(shí)際上,這點(diǎn)在Jenkins的Jacoco插件首頁(yè)就有說(shuō)明: (點(diǎn)擊放大圖像) 但是我當(dāng)時(shí)沒(méi)注意,所以覆蓋率數(shù)據(jù)一直出不來(lái),折騰了好一會(huì),最后還是在同事的幫助下找到問(wèn)題了。 遇到的坑,以及好的practice建議接下來(lái)講講我們遇到的一些坑,以及一些好的practice建議。 1. Native libary無(wú)論是純JUnit還是Robolectric,都不支持load native library,會(huì)報(bào)UnsatisfiedLinkError的錯(cuò)。所以如果你的被測(cè)代碼里面用到了native lib,那么可能需要給System.loadLibrary加上try catch。 如果是被測(cè)代碼用到的第三方lib,而里面用到了native lib的話,一般有兩種解決辦法,一種是將用到native lib的第三方類外面自己在包一層,然后在測(cè)試的情況下mock掉。第二種是用Robolectric,給那個(gè)類創(chuàng)建一個(gè)shadow class。 第一種方法的好處是可以在測(cè)試的時(shí)候隨時(shí)改變這個(gè)類的返回值或行為,缺點(diǎn)是需要另外創(chuàng)建一個(gè)wrapper類,會(huì)有點(diǎn)繁瑣。第二種方式不能隨時(shí)改變這個(gè)類的行為,但是寫(xiě)起來(lái)非常簡(jiǎn)單。所以,看自己的需要,選擇相應(yīng)的方法。 這兩種方法,也是解決static method, final class/method不能mock的主要方式。 2. 盡量寫(xiě)出易于測(cè)試的代碼static method、直接new object、singleton、Global state等等這些都是一些不利于測(cè)試的代碼方式,應(yīng)該盡量避免,用依賴注入來(lái)代替這些方式。 3. 不要重復(fù)你的unit test比如說(shuō)你使用了一個(gè)builder模式來(lái)創(chuàng)建了一個(gè)類,這個(gè)builder有一個(gè)validator,來(lái)validate一些參數(shù)情況。那么這種情況,builder跟validator分開(kāi)測(cè),用各種正確的錯(cuò)誤的參數(shù)情況去測(cè)試validator,然后測(cè)builder的時(shí)候,就不用遍歷各種有效的跟無(wú)效的參數(shù)去測(cè)試了。 因?yàn)槿绻@樣的話,到時(shí)候Validator的邏輯改了,那么針對(duì)Validator的測(cè)試跟針對(duì)Builder的測(cè)試都要修改,這個(gè)其實(shí)是重復(fù)的。這里只需要測(cè)試這個(gè)builder里面有一個(gè)Validator就好了。 4. 公共的單元測(cè)試library如果你們公司也是組件化開(kāi)發(fā)的話,抽出一個(gè)公共的單元測(cè)試類庫(kù)來(lái)做單元測(cè)試,里面可以放一些公共的helper、utils、rules等等,這個(gè)可以極大的提高寫(xiě)單元測(cè)試的速度。 5. 把安卓里面的“純java”代碼copy一份到自己的項(xiàng)目里面安卓里面有些類其實(shí)跟安卓沒(méi)太大關(guān)系的,比如說(shuō)TextUtils、Color等等,這些類完全可以把代碼copy出來(lái),放到自己的項(xiàng)目里面,然后其他地方就用這個(gè)類,這樣也能部分?jǐn)[脫android的依賴,使用JUnit而不是Robolectric,提高運(yùn)行test的速度。 6. 充分發(fā)揮JUnit Rule的作用JUnit Rule是個(gè)很強(qiáng)大的工具,然而知道的人卻不多。它的基本作用是,讓你在執(zhí)行某個(gè)測(cè)試方法前后,可以做一些事情。如果你的好幾個(gè)測(cè)試類里面有很多的共同的setup、teardown工作,你可能會(huì)傾向于使用繼承,結(jié)合@Before、@After來(lái)減少duplication,這里更建議大家使用JUnit Rule來(lái)實(shí)現(xiàn)這個(gè)目的,而不是用繼承,這樣可以有更大的靈活性。 比如,為了方便測(cè)試Activity的method,我們有一個(gè)ActivityRule,在跑一個(gè)測(cè)試方法之會(huì)啟動(dòng)target Activity,然后跑完以后自動(dòng)finish這個(gè)activity。 其中一個(gè)比較有趣的用JUnit Rule實(shí)現(xiàn)的功能,是實(shí)現(xiàn)類似于BDD測(cè)試框架的命名方式。做單元測(cè)試的時(shí)候,你經(jīng)常需要為同一個(gè)方法寫(xiě)好幾個(gè)測(cè)試方法,每個(gè)測(cè)試方法測(cè)試不同的點(diǎn)。為了讓命名更具可讀性,我們往往會(huì)把名字寫(xiě)的很長(zhǎng),在這種情況下,如果用駝峰命名的話,需要不斷切換大小寫(xiě),寫(xiě)起來(lái)麻煩,可讀性也不高。如果用下劃線的話,寫(xiě)起來(lái)也很麻煩。如果你使用過(guò)BDD的一些框架(比如RSpec、Cucumber、Jasmine等),你就會(huì)異常懷念那種“命名”方式。如果你沒(méi)用過(guò)的話,那種“命名”方式大概是這樣的:
這里的關(guān)鍵是,當(dāng)測(cè)試方法失敗的時(shí)候,這個(gè)字符串是要能被加到錯(cuò)誤信息里面的。我們做了個(gè)JUnit Rule來(lái)達(dá)到這個(gè)效果。做法是結(jié)合一個(gè)自定義的annotation,這個(gè)annotation接收一個(gè)String,來(lái)描述這個(gè)測(cè)試方法的測(cè)試目的。在Rule里面將這個(gè)annotation讀出來(lái),如果測(cè)試沒(méi)通過(guò)的話,把這個(gè)描述性的String加到輸出的error message里面。這樣在批量運(yùn)行的時(shí)候,一看就知道沒(méi)通過(guò)的測(cè)試是測(cè)什么東西的。而測(cè)試方法的命名則可以比較隨意。
如果運(yùn)行失敗,得到如下的結(jié)果
關(guān)于JUnit Rule的使用,大家可以自行g(shù)oogle一下,也不難。 7. 善于利用AndroidStudio來(lái)加快你寫(xiě)測(cè)試的速度AndroidStudio有很多feature可以幫助我們更快的寫(xiě)代碼,比如code generation和live template。這點(diǎn)對(duì)于寫(xiě)正式代碼也適用,不過(guò)對(duì)于寫(xiě)測(cè)試代碼來(lái)說(shuō),效果更為突出。因?yàn)榇蟛糠譁y(cè)試代碼的結(jié)構(gòu)、風(fēng)格都是類似的,在這里live template能起非常大的作用。此外,如果你先寫(xiě)測(cè)試,可以直接寫(xiě)一些還不存在的Class或method,然后alt+enter讓AndroidStudio自動(dòng)幫你生成。 8. 不要追求完美剛開(kāi)始的時(shí)候,不用追求測(cè)試代碼的質(zhì)量,也不用追求完美,如果有些地方不好寫(xiě)測(cè)試,可以先放放,以后再來(lái)補(bǔ),有部分測(cè)試總比沒(méi)有測(cè)試好。Martin Fowler說(shuō)過(guò)
然而等你熟悉寫(xiě)測(cè)試的方法以后,強(qiáng)烈建議先寫(xiě)測(cè)試!因?yàn)槿绻阆葘?xiě)了正式代碼,那你對(duì)這寫(xiě)代碼是如何work的已經(jīng)有一個(gè)印象了,因此你往往會(huì)寫(xiě)出能順利通過(guò)的測(cè)試,而忽略一些會(huì)讓測(cè)試不通過(guò)的情況。如果先寫(xiě)測(cè)試,則能考慮得更全面。 9. 未來(lái)的打算使用Groovy和RoboSpock或者是Kotlin和Spek,真正實(shí)現(xiàn)BDD,這是很可能的事情,只是目前我們這邊還沒(méi)太多那方面的實(shí)踐,因此就不說(shuō)太多了。以后有一定實(shí)踐了,到時(shí)候可以再更大家交流。 文中部分代碼:https://github.com/ChrisZou/android-unit-testing-tutorial QA環(huán)節(jié)Q:如何測(cè)試界面交互?如點(diǎn)擊拖動(dòng)等。
Q:我也是后來(lái)才接觸代碼測(cè)試的,然后開(kāi)始喜歡上寫(xiě)代碼測(cè)試,但當(dāng)嘗試為以前的代碼寫(xiě)代碼測(cè)試的時(shí)候,發(fā)現(xiàn)以前的結(jié)構(gòu)很難寫(xiě)代碼測(cè)試,請(qǐng)問(wèn)你們也有遇到這種情況么?如何解決。
Q:自繪控件一般怎么去做自動(dòng)化測(cè)試?
Q:業(yè)務(wù)測(cè)試數(shù)據(jù),是自己本地寫(xiě)的邏輯,還是結(jié)合服務(wù)器的真實(shí)邏輯?
Q:MVP的情況下view和presenter的回調(diào)函數(shù)需要做測(cè)試嗎,如果需要怎么做?
Q:對(duì)于依賴環(huán)境的測(cè)試,比如有無(wú)網(wǎng)絡(luò),不同的網(wǎng)絡(luò)測(cè)試類型,不同的網(wǎng)絡(luò)類型,網(wǎng)絡(luò)超時(shí)等,這種怎么去做單元測(cè)試比較好?在比如測(cè)試試寫(xiě)文件的方法,怎么去構(gòu)造剩余空間不足、空間足夠的環(huán)境?
Q:為啥方法名不是駝峰命名法?
Q:robolectric一般只能模擬點(diǎn)擊到一個(gè)子控件,但是自繪的控件可能不滿足,自繪控件一般是為了較少layout的嵌套,而實(shí)現(xiàn)自繪的,點(diǎn)擊控件的不同區(qū)域可能會(huì)觸發(fā)不同的事件,以前我們的做法非常拿到,需要專門(mén)去根據(jù)這個(gè)自繪控件去這一大堆的測(cè)試代碼,不知道有沒(méi)有什么好的方法?
感謝徐川對(duì)本文的審校。 |
|
|