日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > Android >内容正文

Android

Android单元测试 - 几个重要问题

發(fā)布時(shí)間:2024/9/21 Android 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android单元测试 - 几个重要问题 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

已經(jīng)一個(gè)月沒(méi)寫(xiě)文章了,由于9月份在plan國(guó)慶旅行計(jì)劃,國(guó)慶前前后后去了14天旅行,所以沒(méi)時(shí)間寫(xiě),哈哈。

言歸正傳,上一篇文章《Android單元測(cè)試 - 如何開(kāi)始?》介紹了幾款單元測(cè)試框架、Junit & Mockito基本用法、依賴(lài)隔離 & Mock概念,本篇主要解答單元測(cè)試中幾個(gè)重要問(wèn)題。

在單元測(cè)試交流微信群,很多新進(jìn)來(lái)的小伙伴,都會(huì)幾個(gè)大同小異的問(wèn)題。我們幾個(gè)老鳥(niǎo)們答完一次又一次(厚顏無(wú)恥地把自己算上^_^),筆者是有點(diǎn)不耐煩了,后來(lái)就等其他同學(xué)回答他們.....其實(shí)大家提的問(wèn)題,歸根到底就是“依賴(lài)問(wèn)題”,jvm依賴(lài)還是android依賴(lài)?用到native方法報(bào)錯(cuò)怎么辦?靜態(tài)方法怎么解決?

于是呢,筆者決定專(zhuān)門(mén)寫(xiě)一篇文章,來(lái)講解這幾個(gè)問(wèn)題。

  • 如何解決Android依賴(lài)?
  • 隔離Native方法
  • 解決內(nèi)部new對(duì)象
  • 靜態(tài)方法
  • RxJava異步轉(zhuǎn)同步

1.如何解決Android依賴(lài)?

小白:“Presenter中用到TextUtils,運(yùn)行junit時(shí)報(bào)'java.lang.RuntimeException: Method isEmpty in android.text.TextUtils not mocked'錯(cuò)誤... 是不是要用robolectric?”

別急,還未到robolectric出場(chǎng)的時(shí)候呢!

由于junit運(yùn)行在jvm上,而jdk沒(méi)有android源碼,所以TextUtils這些在android sdk中的類(lèi),運(yùn)行junit時(shí)就引用不上了。既然jdk沒(méi)有,我們就自己加唄!

在test/java目錄下,創(chuàng)建android.text.TextUtils類(lèi)

  • package?android.text;?
  • ?
  • public?class?TextUtils?{?
  • ?
  • ????public?static?boolean?isEmpty(CharSequence?str)?{?
  • ????????if?(str?==?null?||?str.equals(""))?{?
  • ????????????return?true;?
  • ????????}?
  • ????????return?false;?
  • ????}?
  • }?
  • 關(guān)鍵是要個(gè)TextUtils同包名、同類(lèi)名、同方法名。注意不是在main/java下創(chuàng)建,不然會(huì)提示Duplicate class found in the file...。單元測(cè)試運(yùn)行妥妥的:

    原理很簡(jiǎn)單,jvm運(yùn)行時(shí)會(huì)找android.text.TextUtils類(lèi),然后找isEmpty方法執(zhí)行。學(xué)過(guò)java反射的同學(xué)都知道,只要知道包名類(lèi)名,就可以拿到Class,知道該類(lèi)某方法名,就可以獲取Method并執(zhí)行。jvm也是類(lèi)似的機(jī)制,只要我們給一個(gè)包名類(lèi)名與android sdk相同的類(lèi),寫(xiě)上方法名&參數(shù)&返回值相同的方法,jvm就能編譯并執(zhí)行。

    (提示:android的View之類(lèi)也能這么搞噢)

    2.隔離Native方法

    小白:“我用到native方法,junit運(yùn)行失敗,robolectric也不支持加載so文件,怎么辦?”

    Model類(lèi):

  • package?com.test.unit;?
  • ?
  • public?class?Model?{?
  • ????public?native?boolean?nativeMethod();?
  • }??
  • 單元測(cè)試:

  • public?class?ModelTest?{?
  • ?
  • ????Model?model;?
  • ?
  • ????@Before?
  • ????public?void?setUp()?throws?Exception?{?
  • ????????model?=?new?Model();?
  • ????}?
  • ?
  • ????@Test?
  • ????public?void?testNativeMethod()?throws?Exception?{?
  • ????????Assert.assertTrue(model.nativeMethod());?
  • ????}?
  • }??
  • run ModelTest... 報(bào)錯(cuò)java.lang.UnsatisfiedLinkError: com.test.unit.Model.nativeMethod()

    上篇文章《Android單元測(cè)試 - 如何開(kāi)始?》講述的“依賴(lài)隔離”,這里要用到了!

    改進(jìn)單元測(cè)試:

  • public?class?ModelTest?{?
  • ?
  • ????Model?model;?
  • ?
  • ????@Before?
  • ????public?void?setUp()?throws?Exception?{?
  • ????????model?=?mock(Model.class);?
  • ????}?
  • ?
  • ????@Test?
  • ????public?void?testNativeMethod()?throws?Exception?{?
  • ????????when(model.nativeMethod()).thenReturn(true);?
  • ?
  • ????????Assert.assertTrue(model.nativeMethod());?
  • ????}?
  • }?
  • 再run一下,pass了:

    這里稍微講講java查找native方法的過(guò)程:

    1).Model.java全名是com.test.unit.Model.java;

    2).調(diào)用native方法nativeMethod()后, jvm會(huì)去找C++層com_test_unit_Model.cpp,再找com_test_unit_Model_nativeMethod()方法,并調(diào)用。

    在APP運(yùn)行過(guò)程,我們會(huì)把cpp編譯成so文件,然后讓APP加載到dalvik虛擬機(jī)。但在單元測(cè)試中,沒(méi)有加載對(duì)應(yīng)的so文件,也沒(méi)有編譯cpp呀!大牛們可能會(huì)嘗試單元測(cè)試時(shí)加載so文件,但完全沒(méi)有必要,也不符合單元測(cè)試的原則。

    所以,我們可以直接用Mockito框架mock native方法就行啦。實(shí)際上,不僅僅是native方法需要mock,很多依賴(lài)的方法、類(lèi)都要mock,下面會(huì)講到更常用的場(chǎng)景。

    (參考《Android JNI原理分析》)

    3.解決內(nèi)部new對(duì)象

    小白:“我在Presenter里new Model,Model依賴(lài)比較多,會(huì)做sql操作,等等.....Presenter依賴(lài)Model返回結(jié)果,導(dǎo)致Presenter沒(méi)法單元測(cè)試?yán)?求大神指點(diǎn)!”

    小白C的例子:Model:

  • public?class?Model?{?
  • ????public?boolean?getBoolean()?{?
  • ????????boolean?bo?=?.......?//?一堆依賴(lài),代碼很復(fù)雜?
  • ????????return?bo;?
  • ????}?
  • }??
  • Presenter:

  • public?class?Presenter?{?
  • ?
  • ????Model?model;?
  • ?
  • ????public?Presenter()?{?
  • ????????model?=?new?Model();?
  • ????}?
  • ?
  • ????public?boolean?getBoolean()?{?
  • ????????return?model.getBoolean());?
  • ????}?
  • }??
  • 錯(cuò)誤的單元測(cè)試:

  • public?class?PresenterTest?{?
  • ?
  • ????Presenter?presenter;?
  • ?
  • ????@Before?
  • ????public?void?setUp()?throws?Exception?{?
  • ????????presenter?=?new?Presenter();?
  • ????}?
  • ?
  • ????@Test?
  • ????public?void?testGetBoolean()?throws?Exception?{?
  • ????????Assert.assertTrue(presenter.getBoolean());?
  • ????}?
  • }??
  • 還是那句話(huà):依賴(lài)隔離。我們隔離Model依賴(lài),即mock Model對(duì)象,而不是new Model()。

    找找以上PresenterTest的問(wèn)題吧:PresenterTest完全不知道Model的存在,意思是無(wú)法mock Model。那么,我們就想辦法把mock Model傳給Presenter——在Presenter構(gòu)造函數(shù)傳參!

    改進(jìn)Presenter:

  • public?class?Presenter?{?
  • ?
  • ????Model?model;?
  • ?
  • ????public?Presenter(Model?model)?{?
  • ????????this.model?=?model;?
  • ????}?
  • ?
  • ????public?boolean?getBoolean()?{?
  • ????????return?model.getBoolean();?
  • ????}?
  • }??
  • 正確的單元測(cè)試:

  • public?class?PresenterTest?{?
  • ????Model?????model;?
  • ????Presenter?presenter;?
  • ?
  • ????@Before?
  • ????public?void?setUp()?throws?Exception?{?
  • ????????model?=?mock(Model.class);//?mock?Model對(duì)象?
  • ?
  • ????????presenter?=?new?Presenter(model);?
  • ????}?
  • ?
  • ????@Test?
  • ????public?void?testGetBoolean()?throws?Exception?{?
  • ????????when(model.getBoolean()).thenReturn(true);?
  • ?
  • ????????Assert.assertTrue(presenter.getBoolean());?
  • ????}?
  • }??
  • 事情就這么解決了。如果你覺(jué)得在Activity直接用默認(rèn)Presenter構(gòu)造函數(shù),在構(gòu)造函數(shù)new Model()比較方便,那就保留默認(rèn)構(gòu)造函數(shù)唄。當(dāng)然使用dagger2就不存在多個(gè)構(gòu)造函數(shù)了,都是構(gòu)造傳參。

    4.靜態(tài)方法

    小白:“大神,我在Presenter用到靜態(tài)方法....”筆者:“行了,知道你要說(shuō)什么。”

    Presenter:

  • public?class?Presenter?{?
  • ?
  • ????public?String?getSignParams(int?uid,?String?name,?String?token)?{?
  • ????????return?SignatureUtils.sign(uid,?name,?token);?
  • ????}?
  • }??
  • 解決方法跟上面【解決內(nèi)部new對(duì)象】大同小異,核心思想還是依賴(lài)隔離。

    1).把sign(...)改成非靜態(tài)方法;

    2).把SignatureUtils作為成員變量;

    3).構(gòu)造方法傳入SignatureUtils;

    4).單元測(cè)試時(shí),把mock SignatureUtils傳給Presenter。

    改進(jìn)后Presenter:

  • public?class?Presenter?{?
  • ????SignatureUtils?mSignUtils;?
  • ?
  • ????public?Presenter(SignatureUtils?signatureUtils)?{?
  • ????????this.mSignUtils=?signatureUtils;?
  • ????}?
  • ?
  • ????public?String?getSignParams(int?uid,?String?name,?String?token)?{?
  • ????????return?mSignUtils.sign(uid,?name,?token);?
  • ????}?
  • }??
  • 5.RxJava異步轉(zhuǎn)同步

    小白:“大神...”

    筆者:“為師掐指一算,料汝會(huì)遇此劫難?!?/p>

    小白:(傳說(shuō)中從入門(mén)到出家?)

  • public?class?RxPresenter?{?
  • ?
  • ????public?void?testRxJava(String?msg)?{?
  • ????????Observable.just(msg)?
  • ??????????????????.subscribeOn(Schedulers.io())?
  • ??????????????????.delay(1,?TimeUnit.SECONDS)?//?延時(shí)1秒?
  • //??????????????????.observeOn(AndroidSchedulers.mainThread())?
  • ??????????????????.subscribe(new?Action1<String>()?{?
  • ??????????????????????@Override?
  • ??????????????????????public?void?call(String?msg)?{?
  • ??????????????????????????System.out.println(msg);?
  • ??????????????????????}?
  • ??????????????????});?
  • ????}?
  • }??
  • 單元測(cè)試

  • public?class?RxPresenterTest?{?
  • ?
  • ????RxPresenter?rxPresenter;?
  • ?
  • ????@Before?
  • ????public?void?setUp()?throws?Exception?{?
  • ????????rxPresenter?=?new?RxPresenter();?
  • ????}?
  • ?
  • ????@Test?
  • ????public?void?testTestRxJava()?throws?Exception?{?
  • ????????rxPresenter.testRxJava("test");?
  • ????}?
  • }??
  • 運(yùn)行RxPresenterTest:

    你會(huì)發(fā)現(xiàn)沒(méi)有輸出"test",為什么呢?

    由于testRxJava里面,Obserable.subscribeOn(Schedulers.io())把線(xiàn)程切換到io線(xiàn)程,并且delay了1秒,而testTestRxJava()單元測(cè)試早已在當(dāng)前線(xiàn)程跑完了。筆者試過(guò),即使去掉delay(1, TimeUnit.SECONDS),還是不會(huì)輸出‘test’。

    可以看到筆者把.observeOn(AndroidSchedulers.mainThread())注釋掉了,我們把那句代碼加上,再跑一下testTestRxJava(),會(huì)報(bào)java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked.:

    這是由于jdk沒(méi)有android.os.Looper這個(gè)類(lèi)及相關(guān)依賴(lài)。

    解決以上兩個(gè)問(wèn)題,我們只要把Schedulers.io()&AndroidSchedulers.mainThread()切換為Schedulers.immediate()就可以了。RxJava開(kāi)發(fā)團(tuán)隊(duì)已經(jīng)為大家想好了,提供了RxJavaHooks和RxAndroidPlugins兩個(gè)hook操作的類(lèi)。

    新建RxTools:

  • public?class?RxTools?{?
  • ????public?static?void?asyncToSync()?{?
  • ????????Func1<Scheduler,?Scheduler>?schedulerFunc?=?new?Func1<Scheduler,?Scheduler>()?{?
  • ????????????@Override?
  • ????????????public?Scheduler?call(Scheduler?scheduler)?{?
  • ????????????????return?Schedulers.immediate();?
  • ????????????}?
  • ????????};?
  • ?
  • ????????RxAndroidSchedulersHook?rxAndroidSchedulersHook?=?new?RxAndroidSchedulersHook()?{?
  • ????????????@Override?
  • ????????????public?Scheduler?getMainThreadScheduler()?{?
  • ????????????????return?Schedulers.immediate();?
  • ????????????}?
  • ????????};?
  • ?
  • ????????RxJavaHooks.reset();?
  • ????????RxJavaHooks.setOnIOScheduler(schedulerFunc);?
  • ????????RxJavaHooks.setOnComputationScheduler(schedulerFunc);?
  • ?
  • ????????RxAndroidPlugins.getInstance().reset();?
  • ????????RxAndroidPlugins.getInstance().registerSchedulersHook(rxAndroidSchedulersHook);?
  • ????}?
  • }??
  • 在RxPresenterTest.setUp()加一句RxTools.asyncToSync();:

  • public?class?RxPresenterTest?{?
  • ????RxPresenter?rxPresenter;?
  • ?
  • ????@Before?
  • ????public?void?setUp()?throws?Exception?{?
  • ????????rxPresenter?=?new?RxPresenter();?
  • ?
  • ????????RxTools.asyncToSync();?
  • ????}?
  • ????...?
  • }??
  • 再跑一次testTestRxJava():

    總算輸出"test",感謝上帝啊!(應(yīng)該打賞下筆者吧^_^)

    讀者有沒(méi)發(fā)現(xiàn)RxTools.asyncToSync()多加了一句RxJavaHooks.setOnComputationScheduler(schedulerFunc),意思將computation線(xiàn)程切換為immediate線(xiàn)程。筆者發(fā)現(xiàn),僅僅添加RxJavaHooks.setOnIOScheduler(schedulerFunc),對(duì)于有delay的Obserable還是未通過(guò),于是順手把computation線(xiàn)程也切換了,于是就可以了。

    還有RxJavaHooks.reset()和RxAndroidPlugins.getInstance().reset(),筆者發(fā)現(xiàn),當(dāng)運(yùn)行大量單元測(cè)試時(shí),有些會(huì)失敗,但單獨(dú)運(yùn)行失敗的單元測(cè)試,又通過(guò)了。百思不得其解后,添加了那兩句.....可以了!

    (關(guān)于RxJavaHooks和RxAndroidPlugins的使用,在很久前的文章 《(MVP+RxJava+Retrofit)解耦+Mockito單元測(cè)試 經(jīng)驗(yàn)分享》已經(jīng)提及過(guò))

    小結(jié)

    筆者:“小白同學(xué),現(xiàn)在你踩過(guò)的坑,填好未?”

    小白:“方丈,啊不,大神,上面幾個(gè)問(wèn)題是解決了,不過(guò)還有其他問(wèn)題。”

    筆者:“不挖坑,怎么填坑呢?以后再給你講講其他單元測(cè)試的玄機(jī)?!?/p>

    小白:“......”

    本文詳述了幾個(gè)單元測(cè)試重要問(wèn)題的解決方法,讀者不難發(fā)現(xiàn),筆者一直強(qiáng)調(diào) 依賴(lài)隔離、依賴(lài)隔離、依賴(lài)隔離,這個(gè)概念在單元測(cè)試中相當(dāng)重要。還搞不懂這個(gè)概念的同學(xué),看多幾次《Android單元測(cè)試 - 如何開(kāi)始?》(又厚顏無(wú)恥地廣告),同時(shí)在實(shí)踐中不斷回顧這個(gè)理念。

    只要解決好這幾個(gè)問(wèn)題,Presenter單元測(cè)試就不難了。還有本文未提及的sqlite、SharedPreferences單元測(cè)試、在后面的文章會(huì)給讀者介紹下。

    感謝讀者對(duì)筆者一直以來(lái)的支持,麻煩點(diǎn)贊&隨手轉(zhuǎn)發(fā),好人一世平安。

    關(guān)于作者

    我是鍵盤(pán)男。在廣州生活,在創(chuàng)業(yè)公司上班,猥瑣文藝碼農(nóng)。喜歡科學(xué)、歷史,玩玩投資,偶爾獨(dú)自旅行。希望成為獨(dú)當(dāng)一面的工程師。




    作者:鍵盤(pán)男 來(lái)源:51CTO

    總結(jié)

    以上是生活随笔為你收集整理的Android单元测试 - 几个重要问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。