溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Android單元測(cè)試中重要的問(wèn)題有哪些

發(fā)布時(shí)間:2022-01-12 20:11:53 來(lái)源:億速云 閱讀:169 作者:iii 欄目:移動(dòng)開(kāi)發(fā)

本篇內(nèi)容主要講解“Android單元測(cè)試中重要的問(wèn)題有哪些”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Android單元測(cè)試中重要的問(wè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?”

Android單元測(cè)試中重要的問(wèn)題有哪些

別急,還未到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)

  1. package android.text; 

  2.  

  3. public class TextUtils { 

  4.  

  5.     public static boolean isEmpty(CharSequence str) { 

  6.         if (str == null || str.equals("")) { 

  7.             return true; 

  8.         } 

  9.         return false; 

  10.     } 


Android單元測(cè)試中重要的問(wèn)題有哪些

關(guān)鍵是要個(gè)TextUtils同包名、同類(lèi)名、同方法名。注意不是在main/java下創(chuàng)建,不然會(huì)提示Duplicate class found in  the file...。單元測(cè)試運(yùn)行妥妥的:

Android單元測(cè)試中重要的問(wè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è)試中重要的問(wèn)題有哪些

上篇文章《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了:

Android單元測(cè)試中重要的問(wèn)題有哪些

這里稍微講講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ō)什么?!?/p>

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ì)遇此劫難。”

小白:(傳說(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:

Android單元測(cè)試中重要的問(wèn)題有哪些

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

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

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

Android單元測(cè)試中重要的問(wèn)題有哪些

這是由于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():

Android單元測(cè)試中重要的問(wèn)題有哪些

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

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

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

到此,相信大家對(duì)“Android單元測(cè)試中重要的問(wèn)題有哪些”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI