您好,登錄后才能下訂單哦!
今天小編給大家分享一下android單元測試的方法是什么的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
正文
首先我們從Model層開始,通過具體代碼來詳盡說明一下一個單元測試覆蓋率100%的測試工程是如何建立的。嚴格意義上講,Model數據層負責數據加載與儲存,是游離于安卓環(huán)境之外的存在,所以它可以不需要借助安卓SDK的支持。使用Junit結合Mockito即可做到100%條件分支覆蓋率的單元測試。如果項目的Model層有安卓依賴,可能就表明此處的代碼需要重構了,這也是單元測試其中的一個意義,讓代碼邏輯更清晰。清除Model層的安卓依賴的另一層面好處是讓測試case更高效,含有android依賴的測試case執(zhí)行最快也需要5秒,但對于一個沒有安卓依賴的Model類,跑完全部case的時間可以降低至毫秒級。所以,去除Model層所不需要的安卓依賴還是很有必要的。
代碼
Model層測試代碼如下:
@RunWith(MockitoJUnitRunner.class) public classWeatherModelTest { privateWeatherModelmodel; @Mock ApiServiceapi; @Mock WeatherDataConvertconvertData; @Mock WeatherRequestListenerlistener; private static finalStringJSON_ROOT_PATH="/json/"; privateStringjsonFullPath; privateWeatherDatanetData; privateMapqueryMap; @Before public voidsetUp() { RxUnitTestTools.openRxTools(); model=newWeatherModel(); } private voidinitResponse() { try{ jsonFullPath= getClass().getResource(JSON_ROOT_PATH).toURI().getPath(); } catch(URISyntaxException e) { e.printStackTrace(); } String json = getResponseString("weather.json"); Gson gson =newGson(); netData= gson.fromJson(json,WeatherData.class); model.setApiService(api); try{ Field field = WeatherModel.class.getDeclaredField("convert"); field.setAccessible(true); field.set(model,convertData); } catch(Exception e) { //reflect error } queryMap=newHashMap<>(); queryMap.put("city","沈陽"); } privateStringgetResponseString(String fileName) { returnFileUtil.readFile(jsonFullPath+ fileName,"UTF-8").toString(); } private voidsetFinalStatic(Field field,Object newValue)throwsException { field.setAccessible(true); Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setint(field,field.getModifiers() & ~Modifier.FINAL); } }
首先通過@Mock注解對需要mock的對象進行初始化,然后我們需要對測試類進行測試case分析,WeatherModelmode類是一個網絡請求數據model,所以這個model類的核心是request函數。首先對request函數進行分析。必須涵蓋的測試點如下:請求參數校驗,請求成功且返回碼正確處理邏輯校驗,請求成功但校驗碼錯誤處理邏輯校驗和請求失敗處理邏輯校驗。同時Model類中還有一個觀察者解綁函數,所以測試case也需要包含解綁函數處理邏輯測試這一項。通過initResponse,我們可以對接口返回值進行模擬,這里采用讀Json文件的方法將接口返回做成Json數據文件,結合服務端的Swagger文檔可以很輕易的實現服務端接口數據模擬。
@Test@SuppressWarnings("unchecked")public voidtestParams() { model.request(listener,"沈陽"); try{ Field fieldParam = WeatherModel.class.getDeclaredField("queryMap"); Field fieldKey = WeatherModel.class.getDeclaredField("CITY"); fieldParam.setAccessible(true); setFinalStatic(fieldKey, true); Map queryMaps = (Map) fieldParam.get(model); String key = (String) fieldKey.get(model); assertEquals("驗證queryMap的Key",key,"city"); String city = queryMaps.get("city"); assertEquals("驗證queryMap的value",city,"沈陽"); } catch(Exception e) { //reflect error} }
對于有參數的Api,***步就是驗證傳參??赡苣銜X得大材小用,但眾多的血淋淋的慘案告訴我們越是細小的東西越容易產生問題,而單元測試就是幫助我們將細小的問題解決在編碼時期而不對外暴露。要驗證參數的正確性,首先我們需要要驗證向queryMap中put的時候是否正確。對于queryMap,我們需要驗證K-V鍵值對的正確性,還是那句防微杜漸,因為queryMap是一個private變量,在正常情況下我們無法獲取到它的值,而為這個變量加一個對業(yè)務毫無用處的get/set方法就顯得太刻意了,我們的目的是為了解決讓代碼更健壯,bug更少,而不是為了測試而測試。拿不到queryMap參數測試還怎么進行?難道單元測試也要從入門到放棄?要其實很多事情都是這樣,當你覺得某個問題完全沒有辦法解決的時候,一定是你考慮的不夠周全。queryMap對象的值我們可以通過Java反射獲得。反射的原理在這里我就不為大家闡述了,在testParams方法中,我們首先通過getDeclaredField獲取了queryMap對象,然后我們需要獲得到put的key。key的獲得使我們陷入了第二個難題,可能你會說,這有什么難的,繼續(xù)反射啊,可這個key是一個private static變量,通過正常的反射是無法拿到key的,最多會拿到一個異常。還是那句,不要放棄尋找解決方案,最終我們發(fā)現只要設置下虛擬機不去檢測私有屬性,即可完成對private static變量的獲取。不要覺得只是很小的一個參數,這么勞師動眾不值得,據不完全統計,每天因為接口key值多寫或是寫錯一個字母而產生的bug不計其數。
@Test@SuppressWarnings("unchecked")public voidtestRequestSuccess() { initResponse(); Mockito.when(api.getWeather(queryMap)).thenReturn(Observable.just(netData)); ArgumentCaptor captor = ArgumentCaptor.forClass(WeatherData.class); model.request(listener,"沈陽"); Mockito.verify(api).getWeather(queryMap); Mockito.verify(listener).showLoading(); Mockito.verify(listener).hideLoading(); Mockito.verify(convertData).convertData(captor.capture()); WeatherData result = captor.getValue(); intstatus = result.getStatus(); assertEquals("驗證code",status,1000); }
保證的參數傳遞的前提下,我們接下來需要對接口返回狀態(tài)進行測試,首先便是成功態(tài)的接口返回。Mockito.when的作用是設定預期返回結果,例如case testRequestSuccess()所要測試的是請求成功且返回碼正確的情況,所以我們對response的預期就是讓它執(zhí)行onNext方法,同時返回我們初始化好的完全正確的接口數據。Mockito.when使得測試代碼可以完全按照我們所預期的執(zhí)行。不過這個聲明必須在方法執(zhí)行之前,即Mockito.when必須比model.request(listener,"沈陽");先執(zhí)行才會生效。Junit提供了豐富的assert斷言機制,借助assert我們可以實現多種情況的測試,然而對于沒有明確返回值的void方法,assert就顯得有些無能為力,因為它無法找到一個標準進行斷言。這時候需要使用mockito的verify方法,它的作用是驗證mock對象的某一個方法是否得到了正確的執(zhí)行Mockito.verify(listener).showLoading();就是驗證加載進度條是否能夠正常顯示,ArgumentCaptor是一個參數捕獲,它可以捕獲onNext返回的數據,通過assert斷言,我們可以驗證成功情況下數據是否正確。數據成功情況下,我們有一個網絡數據向視圖數據轉換的過程,這個轉換方法是在convert類中執(zhí)行的操作,因為我們做的是單元測試而非集成測試,所以基于WeatherModel這個測試類,我們只需要驗證到convertData()這個函數是否正確得到了調用即可,數據轉換的內容由Convert類的單元測試進行跟蹤即可。
@Testpublic voidtestStatusError() { initResponse(); netData.setStatus(1001); Mockito.when(api.getWeather(queryMap)).thenReturn(Observable.just(netData)); ArgumentCaptor captor = ArgumentCaptor.forClass(WeatherData.class); model.request(listener,"沈陽"); Mockito.verify(api).getWeather(queryMap); Mockito.verify(listener).showLoading(); Mockito.verify(listener).fail(null,ServerCode.get(netData.getStatus()).getMessage()); }
在實際開發(fā)過程中,服務端通常會對同一接口的不同狀態(tài)做成不同的服務應答碼,雖然返回非常態(tài)應答碼的時候網絡請求也是成功,但它卻是有別于常態(tài)服務端應答的另一種情況。所以,這里需要對非常態(tài)服務應答碼進行一個條件分支的測試。testStatusError ()的測試方法與testRequestSuccess()類似,只是我們這次的status模擬值由成功的status換成了一個異常status,同時,驗證的函數執(zhí)行也變成了listener的失敗方法
@Testpublic voidtestRequestFail() { initResponse(); Exception exception =newException("exception"); Mockito.when(api.getWeather(queryMap)).thenReturn(Observable.error(exception)); model.request(listener,"沈陽"); Mockito.verify(listener).fail(null,"exception"); }
Request是一個接口,我們不能夠保證每次請求我們的服務器都能夠給與準確應答,同時用戶在發(fā)出請求的時候我們也不能夠保證用戶所處的網絡狀態(tài)是否通暢。所以我們在設計Model類的時候也要將非常態(tài)考慮在內,對接口的異常情況進行處理,有時候我們需要自己創(chuàng)造一些異常來驗證我們代碼的健壯程度。同樣的,我們的測試類也需要有一個專門的方法來保證異常態(tài)的測試。testRequestFail()的測試方法與成功的方法的不同之處在于我們首先我們需要mock的不是接口數據,而是一個異常,Exception exception = new Exception("exception");注意,這個Exception中的參數即是異常信息,因為我們的fail方法中有異常信息的顯示,所以這個參數是必須要加上的,否則e.getLocalizedMessage()會拋出NPE。另外,這個時候的Mockito.when的期望也有所改變,這次我們期望的是函數執(zhí)行onError方法。
@Testpublic voidtestCancelRequest() { Subscription subscription =mock(Subscription.class); model.setSubscription(subscription); model.cancelRequest(); verify(subscription).unsubscribe(); }
Model類中***一個case是testCancelRequest()它的作用是,在合適的時候解綁request,我們的網絡請求是異步的,也就是說當我們調用請求的activity或是fragment destroy的時候,如果我們沒有解除綁定,是存在內存泄漏風險的。當然,我們能想到的問題,Rxjava的維護者們也一定想到了,Subscription就是方便我們在生命周期結束的時候對Rx解綁。驗證方法很簡單,還是通過verify方法,驗證解綁方法是否得到了正確執(zhí)行。
dependencies { classpath'com.vanniktech:gradle-android-junit-jacoco-plugin:0.6.0' }
至此我們已經完成了對model的全覆蓋測試,點擊測試類前面的運行按鈕,可以看到所有測試類運行的情況,綠色代表成功,紅色代表存在問題,可以通過下方的Log日志查看引起測試失敗的問題點進行改正,借助Jacoco統計工具可以看到單元測試覆蓋率的情況。之所以選擇使用Jacoco而不是IDE自帶的Coverage是因為在測試&條件分支的情況下Coverage存在漏洞,導致沒有達到全覆蓋的測試顯示已覆蓋完全。Jacoco的AndroidStudio集成網絡資源并不多,集成方法不是存在潛在漏洞就是過于繁瑣。經過兩天的不斷搜索,終于發(fā)現了一個史上最簡單集成方法,只需要在主工程的gradle文件中添加一個Jacoco插件,gradle就會生成一個Jacoco Task,雙擊運行即可生成一份Html覆蓋率報告。運行我們的model測試類,從jacoco生成的html可以看到,我們的model已經達到了100%的全覆蓋。既然如此,我們是不是就可以認為MVP的M層已經ok了呢?等等,我們好像遺漏了點什么,沒錯,onNext情況下的數據轉換類還沒有測試,下面我們來對convert類進行一下測試。
首先們來看看convert類代碼:
/** * Author : YangHaoyi on 2017/6/28. * Email : yanghaoyi@neusoft.com * Description :網絡數據與視圖數據轉換器 * Change : YangHaoYi on 2017/6/28. * Version : V 1.0 */ open classWeatherDataConvert { open funconvertData(netData: WeatherData):WeatherViewData{ valviewData= WeatherViewData() viewData.temperature= netData.data?.temperature?:0.0viewData.weatherType= netData.data?.weatherType?:1viewData.ultraviolet= netData.data?.ultraviolet?:0viewData.rainfall= netData.data?.rainfall?:"0"viewData.hourTemperature= netData.data?.hourTemperature?:"10"viewData.windPower= netData.data?.windPower?:"2"returnviewData } }
從代碼可以看出我們的convert類看起來有一些的奇怪,每錯,因為它并不是java代碼,它是kotlin。好好的java工程為什么要混入kotlin,單單只是為了炫技么?當然不是,數據轉換類的作用是對網絡數據進行判空并包裝成視圖數據,我們都知道在java中的判空,需要層層嵌套,例如,我們需要判斷Student類中的Score類中的EnglishScore字段,我們的寫法如下:
if(Student!=null&&Student.getScore()!=null&&Student.getScore().getEnglishScore()!=null){}
這是一個很多層的判斷,而對于kotlin我們只需要寫Student?.score?.englishScore即可,代碼量巨減有沒有。對于kotlin的特性,有興趣的同學可以移步官網去詳細了解。
讓我們回歸單元測試,convert類是一個數據判空類,它的作用是對數據進行組裝并賦予默認初值,因為服務端的數據不可控,作為手機端我們不能把用戶體驗完全寄托于后端的兄弟,因為放過任何一個null數據對于App都是一個Crash。所以我們的測試點就是,這個類是否達到了當數據為空的時候賦予默認值,當數據不為空的時候取網絡數據值的作用。這里選取一個比較有代表性的testTemperature為例,首先設定模擬WeatherData的值為10D,因為網絡數據有值,所以會取網絡數據的值即10D,通過assertEquals可以進行斷言比對驗證,不過有一個需要注意的是double型的斷言assertEquals(message,double1,double2)是不可用的,直接運行的話會報測試失敗。Double的比對需要加上一個誤差值,這里給一個誤差值0.1D,再次運行,測試條變綠。同時我們需要測試當WeatherData為空的情況下,viewData是否被賦予了默認值0.0。以此類推,我們需要對每一條數據進行校驗,并包裝成視圖數據。
/** * Author : YangHaoyi on 2017/7/7. * Email : yanghaoyi@neusoft.com * Description : * Change : YangHaoYi on 2017/7/7. * Version : V 1.0 */ public classWeatherDataConvertTest { privateWeatherDataConvertconvert; private static doubleDETAL=0.1D; @Beforepublic voidsetUp(){ convert=newWeatherDataConvert(); } @Testpublic voidtestTemperature(){ WeatherData netData =newWeatherData(); WeatherData.DataBean dataBean =newWeatherData.DataBean(); dataBean.setTemperature(10D); netData.setData(dataBean); WeatherViewData viewData =convert.convertData(netData); //斷言double不可以用assertEquals(message,double1,double2)//需要改用下面的方法,DETAL為誤差值assertEquals(viewData.getTemperature(),10D,DETAL); } @Testpublic voidtestTemperatureNull(){ WeatherData netData =newWeatherData(); WeatherData.DataBean dataBean =newWeatherData.DataBean(); netData.setData(dataBean); WeatherViewData viewData =convert.convertData(netData); //斷言double不可以用assertEquals(message,double1,double2)//需要改用下面的方法,DETAL為誤差值assertEquals(viewData.getTemperature(),0D,DETAL); } }
Convert類的順利執(zhí)行標志著Model層的測試圓滿結束,下面讓我們來看一看MVP架構下的第二順位View層的測試,如果我們不借助UI測試框架直接運行UI測試是無法得到預期的驗證的,因為我們只會得到一個運行時異常??墒俏覀冊跇嫿üこ讨耙呀浵螺d了對應版本的安卓Sdk,為什么還是會拋出異常呢?在真機或是模擬器上面為什么不會呢?是不是IDE只為我們提供了工程的開發(fā)與編譯環(huán)境,并沒有提供工程的運行環(huán)境呢?引用Linus Torvalds的那句經典的RTFSC,讓我們通過源碼來一點點驗證我們的猜想。首先我們找到SDK對應的android.jar文件,然后隨便找個工程add as library,以我們最常用的Activity為例,源碼如下:
public WindowManager getWindowManager() { throw newRuntimeException("Stub!"); } public Window getWindow() { throw newRuntimeException("Stub!"); } public LoaderManager getLoaderManager() { throw newRuntimeException("Stub!"); } public View getCurrentFocus() { throw newRuntimeException("Stub!"); } protected void onCreate(BundlesavedInstanceState) { throw new RuntimeException("Stub!"); } public void onCreate(BundlesavedInstanceState, PersistableBundle persistentState) { throw newRuntimeException("Stub!"); }
我們可以清除的看到所有的方法都不約而同的拋出了RuntimeException("Stub!"),這也就是我們的測試case無法進行的原因。為了應對UI單元測試難以推進的現狀,谷歌推出了一套名為Espresso的UI單元測試框架,由于是官方的框架,所以在工程的運行以及相關資料的跟進都做的比較完善。然而Espresso的短板也非常明顯,Espresso必須借助于安卓模擬器或是真機環(huán)境才能夠運行,也正是因為需要在安卓設備上運行,Espresso的運行速度非常緩慢,使之與Jenkins相結合進行自動化構建更是難上加難。這不禁讓我陷入沉思,如果UI單元測試需要如此的大費周章,那是否還有測下去的必要?不過很快迭代的bug統計就打消了我放棄UI只做邏輯測試的念頭。我們手機組在迭代過程中的UI與邏輯bug比基本可以達到5比1,也就是說有絕大多數問題產生在了視圖層,單元測試的目的是減少bug產生,而目前UI就是我們***的痛點,UI單元測試勢在必行。經過不斷的資源搜索,最終我到了一個可以不借助安卓設備的UI測試框架Robolectric,它的設計思路是通過實現一套JVM能運行Android代碼,從而做到脫離Android環(huán)境進行測試。由于robolectric需要從oss.sonatype.org下載一些必要的依賴包,但是oss.sonatype.org是國外的網站,下載速度比較緩慢。這里需要修改整個工程的build.gradle文件,修改mavenCentral()為阿里云{"http://maven.aliyun.com/nexus/content/groups/public/"} 的代理。
Robolectric的依賴為:
testCompile'org.robolectric:robolectric:3.3.2'
運行Robolectric需要首先對測試類進行配置,如下:
@RunWith(MyRobolectricTestRunner.class)@Config(constants= BuildConfig.class,sdk=24)
MyRobolectricTestRunner為自定義的指向阿里云的配置文件,BuildConfig為當前model的BuildConfig文件,sdk為使用的sdk版本,之所以指定sdk版本是因為Robolectric需要下載對應sdk的鏡像資源,指定版本就會使用本地已經下載好的sdk資源。***次運行測試的時候會自動到阿里云去下載相關文件,然后會在系統的C盤下生成一個.m2文件夾,如果依舊下載緩慢,可直接拷貝.m2文件夾到自己電腦的相對目錄下直接使用。Robolectric幾乎可以測試一切安卓方法,使用也是非常簡單。例如:
@Beforepublic voidsetUp() { activity= Robolectric.setupActivity(WeatherActivity.class); }
實現的便是創(chuàng)建一個Activity,一行代碼即可模擬activity的創(chuàng)建與運行。一行代碼就解決了一直困擾我們對于android環(huán)境無法獲取的苦惱。有了Activity對象,瞬間覺得可以解決所有問題。例如測試頁面的跳轉:
@Testpublic voidtestToHelpCenter(){ view.toHelpCenter(); //設置期待IntentIntent expectedIntent =newIntent(activity,WeatherHelpCenterActivity.class);//獲取實際IntentIntent actualIntent = ShadowApplication.getInstance().getNextStartedActivity();//通過Assert驗證Assert.assertEquals(expectedIntent.getComponent(),actualIntent.getComponent()); }
設置好當前頁面與跳轉頁面,Robolectric就能夠幫助我們模擬出我們所期待的Intent,同時通過ShadowApplicaiton可以獲取到模擬運行后的實際Intent的值,結合Junit即可完成對Intent的驗證,進而驗證頁面跳轉邏輯。
TextView是我們在開發(fā)過程中最常用也是最容易出錯的一個UI組件,尤其是團隊的設計師是一個非常把不同地方的文案設計得非常想象而又有著細微差別的時候,我們非常容易多打或是少打一個字,又或是錯別或是形近字。為了保證產品質量,我們不得不一遍又一遍的比對UI稿件,錙銖必較,逐字觀察,簡直苦不堪言。所謂程序即生活,難道我們生活中就沒有這種校驗文字的困擾么?生活中我們又都是怎么解決的呢?記得許多年前時不時會看到有人去ATM轉賬轉錯的新聞,今年來倒是很少有這樣的新聞了,原因就在于銀行對于銀行卡號作了二次校驗。對于TextView的測試也是利用了二次校驗的方法,***次文字使用業(yè)務代碼,第二次代碼使用測試代碼進行校驗,如果兩次不一致則證明文字存在問題。這樣就可以有效的避免了靠肉眼比對的不確定性,讓程序去驗證程序。
@Testpublic voidtestShowTemperature(){ //模擬視圖數據WeatherViewData viewData =newWeatherViewData(); viewData.setTemperature(23.1D); view.updateCache(viewData); //執(zhí)行待測函數view.showTemperature();//通過Id獲得view實體TextView tvTemperature = (TextView)activity.findViewById(R.id.tvTemperature); String text = tvTemperature.getText().toString(); //驗證文字顯示assertEquals("驗證溫度",text,"23.1"); }
首先通過view.showTemperature();調用執(zhí)行函數,在通過Id找到對應的TextView組件,通過getText獲取TextView的顯示文字,再通過Junit的aseertEquals進行字符串驗證即可。如果發(fā)生比對失敗,通過下方的Log提示click to see difference即可準確的看到差異點。
Robolectric對于提示Tost的測試也是非常的簡單,只需要:
@Testpublic voidtestShowDataError(){ view.showDataError(); assertEquals("數據轉換異常",ShadowToast.getTextOfLatestToast()); }
測試Resource中的顏色:
@Testpublic voidtestInitTitle(){ TextView tvTitle = (TextView)activity.findViewById(R.id.tvTitle); view.initTitle(); String title = tvTitle.getText().toString(); assertEquals("驗證標題初始化",title,"幫助中心"); Application application = RuntimeEnvironment.application; ColorStateList color = ColorStateList.valueOf(application.getResources().getColor(R.color.colorWhite)); assertEquals("驗證顏色",color,tvTitle.getTextColors()); }
測試Dialog:
@Testpublic voidtestShowTelDialog(){ view.showTelDialog(); //因為提示框 dialog 在 view 中屬于私有變量,不需要對外暴露方法,如果為了測試而寫一個get set 方法似乎太過牽強//所以采用 Java 反射的方法獲取dialog對象try{// /通過類的字節(jié)碼得到該類中聲明的所有屬性,無論私有或公有Field field = WeatherHelpCenterImpl.class.getDeclaredField("telDialog");// 設置訪問權限(這點對于有過android開發(fā)經驗的可以說很熟悉)field.setAccessible(true);// 得到私有的變量值Object dialog = field.get(view); TConfirmDialog telDialog = (TConfirmDialog) dialog; //獲取到Dialog對象之后,再通過反射獲取Dialog中TextView對象Field fieldDialog = TConfirmDialog.class.getDeclaredField("tvTitle");// 設置訪問權限fieldDialog.setAccessible(true);//獲取telDialog中的TextView對象Object title = fieldDialog.get(telDialog); TextView tvTitle = (TextView) title; //通過assert方法驗證標題assertEquals("驗證標題",tvTitle.getText().toString(),"客服電話");//獲取到Dialog對象之后,再通過反射獲取Dialog中TextView對象fieldDialog = TConfirmDialog.class.getDeclaredField("tvConfirm");//獲取telDialog中的TextView對象Object confirm = fieldDialog.get(telDialog); TextView tvConfirm = (TextView) confirm; //通過assert方法驗證標題assertEquals("驗證確定按鈕",tvConfirm.getText().toString(),"撥打電話");//獲取到Dialog對象之后,再通過反射獲取Dialog中TextView對象fieldDialog = TConfirmDialog.class.getDeclaredField("tvCancel");//獲取telDialog中的TextView對象Object cancel = fieldDialog.get(telDialog); TextView tvCancel = (TextView) cancel; //通過assert方法驗證標題assertEquals("驗證取消按鈕",tvCancel.getText().toString(),"取消"); } catch(Exception e) { //error} }
Dialog的測試點需要包括Dialog的顯示與隱藏,Dialog的提示文字與按鈕的文字顯示,因為很多是私有變量,所以這里用到了一些Java反射來幫助獲取對象。
目前為止,我們已經完成了對Model層與View層的測試,MVP三兄弟只剩下P層還沒有測試,下面我們就來看看P層該如何測試。P層作為M層與V層的紐帶,起到了隔離視圖與數據直接交互的作用。因為P層持有的只是V的接口,所以P層也可以抽離成簡單的純Java測試。讓我們先來看看P層的測試代碼:
/** * Created by YangHaoyi on 2017/7/8. * Email : yanghaoyi@neusoft.com * Description : * Version : */ public classWeatherPresenterTest { privateWeatherPresenterpresenter; privateIWeatherViewview; privateWeatherControlcontrol; privateWeatherModelweatherModel; privateWeatherRequestListenerlistener; @Beforepublic voidsetUp(){ view=mock(IWeatherView.class); control=mock(WeatherControl.class); weatherModel=mock(WeatherModel.class); listener=mock(WeatherRequestListener.class); presenter=newWeatherPresenter(view); presenter.updateWeatherModel(weatherModel); presenter.updateControl(control); presenter.updateListener(listener); } @Testpublic voidtestRequest(){ presenter.request(); verify(weatherModel).request(listener,view.getLocationCity()); } @Testpublic voidtestCancelRequest(){ presenter.cancelRequest(); verify(weatherModel).cancelRequest(); } @Testpublic voidtestShowHourTemperature(){ presenter.showHourTemperature(); verify(control).buttonWasPressed(WeatherControl.TEMPERATURE); } @Testpublic voidtestShowPrecipitation(){ presenter.showPrecipitation(); verify(control).buttonWasPressed(WeatherControl.PRECIPITATION); } @Testpublic voidtestShowWindPower(){ presenter.showWindPower(); verify(control).buttonWasPressed(WeatherControl.WINDPOWER); } @Testpublic voidtestToHelpCenter(){ presenter.toHelpCenter(); verify(view).toHelpCenter(); } }
由于這只是一個示例Demo,沒有過多的業(yè)務邏輯,結合了幾個簡單的設計模式,Presenter的代碼變成了絕大多數的順序執(zhí)行,通過Mockito的verify即可完成驗證。這里需要說明一下的是之所以結合設計模式是因為單元測試的原則是每一個條件分支都需要有一條測試Case做保證,對于多分支甚至是多嵌套分支就會比較繁瑣,需要寫大量的重復代碼,同時也增大了漏測的幾率,適當的添加設計模式可以很好的彌補這一點,將嵌套條件判斷測底刪除,極大程度減少甚至刪除條件判斷。經過完善代碼后的單元測試,測試的只是一些簡單的if/else單分支判斷。驗證方法與Model層的測試方法大同小異,借助Junit與Mockito我們可以輕易的實現Presenter層的測試。
以上就是“android單元測試的方法是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業(yè)資訊頻道。
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。