千家信息网

Android单元测试中重要的问题有哪些

发表于:2024-11-19 作者:千家信息网编辑
千家信息网最后更新 2024年11月19日,本篇内容主要讲解"Android单元测试中重要的问题有哪些",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"Android单元测试中重要的问题有哪些"吧!1.
千家信息网最后更新 2024年11月19日Android单元测试中重要的问题有哪些

本篇内容主要讲解"Android单元测试中重要的问题有哪些",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"Android单元测试中重要的问题有哪些"吧!

1.如何解决Android依赖?

小白:"Presenter中用到TextUtils,运行junit时报'java.lang.RuntimeException: Method isEmpty in android.text.TextUtils not mocked'错误... 是不是要用robolectric?"

别急,还未到robolectric出场的时候呢!

由于junit运行在jvm上,而jdk没有android源码,所以TextUtils这些在android sdk中的类,运行junit时就引用不上了。既然jdk没有,我们就自己加呗!

在test/java目录下,创建android.text.TextUtils类

  1. package android.text;

  2. public class TextUtils {

  3. public static boolean isEmpty(CharSequence str) {

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

  5. return true;

  6. }

  7. return false;

  8. }

  9. }


关键是要个TextUtils同包名、同类名、同方法名。注意不是在main/java下创建,不然会提示Duplicate class found in the file...。单元测试运行妥妥的:

原理很简单,jvm运行时会找android.text.TextUtils类,然后找isEmpty方法执行。学过java反射的同学都知道,只要知道包名类名,就可以拿到Class,知道该类某方法名,就可以获取Method并执行。jvm也是类似的机制,只要我们给一个包名类名与android sdk相同的类,写上方法名&参数&返回值相同的方法,jvm就能编译并执行。

(提示:android的View之类也能这么搞噢)

2.隔离Native方法

小白:"我用到native方法,junit运行失败,robolectric也不支持加载so文件,怎么办?"

Model类:

package com.test.unit;  public class Model {     public native boolean nativeMethod(); }

单元测试:

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... 报错java.lang.UnsatisfiedLinkError: com.test.unit.Model.nativeMethod()

上篇文章《Android单元测试 - 如何开始?》讲述的"依赖隔离",这里要用到了!

改进单元测试:

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方法的过程:

1).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框架mock native方法就行啦。实际上,不仅仅是native方法需要mock,很多依赖的方法、类都要mock,下面会讲到更常用的场景。

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

3.解决内部new对象

小白:"我在Presenter里new Model,Model依赖比较多,会做sql操作,等等.....Presenter依赖Model返回结果,导致Presenter没法单元测试啦!求大神指点!"

小白C的例子:Model:

public class Model {     public boolean getBoolean() {         boolean bo = ....... // 一堆依赖,代码很复杂         return bo;     } }

Presenter:

public class Presenter {      Model model;      public Presenter() {         model = new Model();     }      public boolean getBoolean() {         return model.getBoolean());     } }

错误的单元测试:

public class PresenterTest {      Presenter presenter;      @Before     public void setUp() throws Exception {         presenter = new Presenter();     }      @Test     public void testGetBoolean() throws Exception {         Assert.assertTrue(presenter.getBoolean());     } }

还是那句话:依赖隔离。我们隔离Model依赖,即mock Model对象,而不是new Model()。

找找以上PresenterTest的问题吧:PresenterTest完全不知道Model的存在,意思是无法mock Model。那么,我们就想办法把mock Model传给Presenter——在Presenter构造函数传参!

改进Presenter:

public class Presenter {      Model model;      public Presenter(Model model) {         this.model = model;     }      public boolean getBoolean() {         return model.getBoolean();     } }

正确的单元测试:

public class PresenterTest {     Model     model;     Presenter presenter;      @Before     public void setUp() throws Exception {         model = mock(Model.class);// mock Model对象          presenter = new Presenter(model);     }      @Test     public void testGetBoolean() throws Exception {         when(model.getBoolean()).thenReturn(true);          Assert.assertTrue(presenter.getBoolean());     } }

事情就这么解决了。如果你觉得在Activity直接用默认Presenter构造函数,在构造函数new Model()比较方便,那就保留默认构造函数呗。当然使用dagger2就不存在多个构造函数了,都是构造传参。

4.静态方法

小白:"大神,我在Presenter用到静态方法...."笔者:"行了,知道你要说什么。"

Presenter:

public class Presenter {      public String getSignParams(int uid, String name, String token) {         return SignatureUtils.sign(uid, name, token);     } }

解决方法跟上面【解决内部new对象】大同小异,核心思想还是依赖隔离。

1).把sign(...)改成非静态方法;

2).把SignatureUtils作为成员变量;

3).构造方法传入SignatureUtils;

4).单元测试时,把mock SignatureUtils传给Presenter。

改进后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异步转同步

小白:"大神..."

笔者:"为师掐指一算,料汝会遇此劫难。"

小白:(传说中从入门到出家?)

public class RxPresenter {      public void testRxJava(String msg) {         Observable.just(msg)                   .subscribeOn(Schedulers.io())                   .delay(1, TimeUnit.SECONDS) // 延时1秒 //                  .observeOn(AndroidSchedulers.mainThread())                   .subscribe(new Action1() {                       @Override                       public void call(String msg) {                           System.out.println(msg);                       }                   });     } }

单元测试

public class RxPresenterTest {      RxPresenter rxPresenter;      @Before     public void setUp() throws Exception {         rxPresenter = new RxPresenter();     }      @Test     public void testTestRxJava() throws Exception {         rxPresenter.testRxJava("test");     } }

运行RxPresenterTest:

你会发现没有输出"test",为什么呢?

由于testRxJava里面,Obserable.subscribeOn(Schedulers.io())把线程切换到io线程,并且delay了1秒,而testTestRxJava()单元测试早已在当前线程跑完了。笔者试过,即使去掉delay(1, TimeUnit.SECONDS),还是不会输出‘test’。

可以看到笔者把.observeOn(AndroidSchedulers.mainThread())注释掉了,我们把那句代码加上,再跑一下testTestRxJava(),会报java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked.:

这是由于jdk没有android.os.Looper这个类及相关依赖。

解决以上两个问题,我们只要把Schedulers.io()&AndroidSchedulers.mainThread()切换为Schedulers.immediate()就可以了。RxJava开发团队已经为大家想好了,提供了RxJavaHooks和RxAndroidPlugins两个hook操作的类。

新建RxTools:

public class RxTools {     public static void asyncToSync() {         Func1 schedulerFunc = new Func1() {             @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",感谢上帝啊!(应该打赏下笔者吧^_^)

读者有没发现RxTools.asyncToSync()多加了一句RxJavaHooks.setOnComputationScheduler(schedulerFunc),意思将computation线程切换为immediate线程。笔者发现,仅仅添加RxJavaHooks.setOnIOScheduler(schedulerFunc),对于有delay的Obserable还是未通过,于是顺手把computation线程也切换了,于是就可以了。

还有RxJavaHooks.reset()和RxAndroidPlugins.getInstance().reset(),笔者发现,当运行大量单元测试时,有些会失败,但单独运行失败的单元测试,又通过了。百思不得其解后,添加了那两句.....可以了!

到此,相信大家对"Android单元测试中重要的问题有哪些"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

0