原文链接:http://www.jianshu.com/p/f5d197a4d83a一个月没写前言了。由于9月国庆出游计划,国庆前后旅游了14天,所以没时间写,哈哈。言归正传,上一篇《Android单元测试 - 如何开始?》介绍了几个单元测试框架,Junit&Mockito的基本使用,依赖隔离&Mock的概念,本文主要回答单元测试中的几个重要问题。在单元测试交流微信群里,很多新人都会有几个类似的问题。我们几个老鸟一连答了一遍(厚颜无耻算自己^_^),作者有点不耐烦了,然后就等其他同学回答了。。。其实大家提的问题都在归根到底是“依赖问题”,jvm依赖还是android依赖?使用native方法报错怎么办?如何解决静态方法?于是,笔者决定专门写一篇文章来解释这些问题。如何解决安卓依赖?隔离Native方法,解决内部new对象静态方法RxJava异步转同步1.如何解决Android依赖?小白:“Presenter中使用了TextUtils,运行junit报'java.lang.RuntimeException:MethodisEmptyinandroid.text.TextUtilsnotmocked'错误...要用robolectric吗?”别着急,现在还不是robolectric登场的时候!由于junit运行在jvm上,而jdk没有android源码,所以TextUtils在androidsdk中运行junit时不能引用该类。既然jdk不存在,那我们自己添加吧!在test/java目录下,创建android.text.TextUtils类packageandroid.text;publicclassTextUtils{publicstaticbooleanisEmpty(CharSequencestr){if(str==null||str.equals("")){returntrue;}returnfalse;}}关键是要有一个具有相同包名、相同名称和相同方法名的TextUtils。注意不要在main/java下创建,否则会提示Duplicateclassfoundinthefile....单元测试正常运行:原理很简单,jvm运行的时候会找到android.text。TextUtils类,然后找到isEmpty方法执行。学过java反射的同学都知道,只要知道包名和类名就可以得到Class,知道类的某个方法名就可以得到Method并执行。jvm也是类似的机制。只要我们给一个和androidsdk同包名和类名的类,写一个同方法名&参数&返回值的方法,jvm就可以编译执行。(提示:androidView也可以这样做。)2.隔离Native方法小白:“我用的是native方法,junit运行失败,robolectric不支持加载so文件,怎么办?”模型类:packagecom.测试单元;publicclassModel{publicnativebooleannativeMethod();}单元测试:publicclassModelTest{Modelmodel;@BeforepublicvoidsetUp()throwsException{model=newModel();}@TestpublicvoidtestNativeMethod()throwsException{Assert.assertTrue(model.nativeMethod());}}运行ModelTest...Errorjava.lang.UnsatisfiedLinkError:com.test.unit.Model.nativeMethod()这里使用了上篇文章《Android单元测试 - 如何开始?》讲的“依赖隔离”!改进单元测试:publicclassModelTest{Modelmodel;@BeforepublicvoidsetUp()throwsException{model=mock(Model.class);}@TestpublicvoidtestNativeMethod()throwsException{when(model.nativeMethod()).thenReturn(true);Assert.assertTrue(model.Model.java的全称是com.test.unit.Model.java;2).调用native方法nativeMethod()后,jvm会在C++层com_test_unit_Model.cpp中找到com_test_unit_Model_nativeMethod()方法并调用。在APP运行过程中,我们会将cpp编译成so文件,然后将APP加载到dalvik虚拟机中。但是在单元测试中,并没有加载对应的so文件,也没有编译cpp!大牛在单元测试的时候可能会尝试加载so文件,但是完全没有必要,也不符合单元测试的原则。因此,我们可以直接使用Mockito框架来mocknative方法。事实上,不仅native方法需要mock,很多依赖的方法和类也需要mock。下面将讨论更常用的场景。(参考《Android JNI原理分析》)3.解决内部新建对象小白:“我在Presenter中新建了一个Model,这个Model有很多依赖,可以做sql操作等等.....Presenter依赖在Model上返回结果,导致Presenter无法进行单元测试!请多多指教!”小白C的例子:Model:publicclassModel{publicbooleangetBoolean(){booleanbo=.......//一堆依赖,代码很复杂returnbo;}}Presenter:publicclassPresenter{Modelmodel;publicPresenter(){model=newModel();}publicbooleangetBoolean(){returnmodel.getBoolean());}}错误的单元测试:publicclassPresenterTest{Presenterpresenter;@BeforepublicvoidsetUp()throwsException{presenter=newPresenter();}@TestpublicvoidtestGetBoolean()throwsException{Assert.assertTrue(presenter.getBoolean());}}还是那句话:依赖隔离。我们隔离Model依赖项,即模拟Model对象,而不是newModel()。寻找上面的PresenterTest问题:PresenterTest完全不知道Model的存在,也就是说它不能mockModel。那么,我们就想办法把mockModel传递给Presenter——在Presenter的构造函数中传递参数!改进Presenter:publicclassPresenter{Modelmodel;publicPresenter(Modelmodel){this.model=model;}publicbooleangetBoolean(){returnmodel.getBoolean();}}正确的单元测试:publicclassPresenterTest{Modelmodel;Presenterpresenter;@BeforepublicvoidsetUp()throwsException{model=mock(Model.class);//mockModel对象presenter=newPresenter(model);}@TestpublicvoidtestGetBoolean()throwsException{when(model.getBoolean)()).thenReturn(true);Assert.assertTrue(presenter.getBoolean());}}就是这样。如果你觉得在Activity中直接使用默认的Presenter构造函数而不是构造函数newModel()更方便,那就保留默认构造函数。当然,使用dagger2时没有多个构造函数,都是构造并传递参数。4.静态方法小白:“大神,我在Presenter中使用静态方法....”作者:“好吧,我知道你想说什么了。”演示者:publicclassPresenter{publicStringgetSignParams(intuid,Stringname,Stringtoken){returnSignatureUtils。sign(uid,name,token);}}解决方法和上面的【解决内部新建对象】类似,核心思想是依赖隔离。1).将sign(...)更改为非静态方法;2).使用SignatureUtils作为成员变量;3).将构造方法传入SignatureUtils;4).单元测试时,将模拟SignatureUtils传递给Presenter。改进的Presenter:publicclassPresenter{SignatureUtilsmSignUtils;publicPresenter(SignatureUtilssignatureUtils){this.mSignUtils=signatureUtils;}publicStringgetSignParams(intuid,Stringname,Stringtoken){returnmSignUtils.sign(uid,name,token);}}5.RxJava异步转同步小白:《大神...》作者:“身为老师,我已经算过,预料到你会遭遇这场劫难。》小白:(传说从入门到出家?)publicclassRxPresenter{publicvoidtestRxJava(Stringmsg){Observable.just(msg).subscribeOn(Schedulers.io()).delay(1,TimeUnit.SECONDS)//延迟1第二//.observeOn(AndroidSchedulers.mainThread()).subscribe(newAction1
