Head First: Android 单元测试最佳实践

   2016-11-13 0
核心提示:单元测试是什么单元测试是针对程序的最小单元来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。一个单元可能是单个程序、类、对象、方法等。 ——维基百科为什么要做单元测试卖个关子,看完文章自然就知道了原来和很多人一样并没有写单元测试的

单元测试是什么

单元测试 是针对  程序的最小单元 来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。一个单元可能是 单个程序、类、对象、方法 等。 ——维基百科

为什么要做单元测试

卖个关子,看完文章自然就知道了

原来和很多人一样并没有写单元测试的习惯,写好一个功能模块之后直接在真机上做自测,看看刚写的功能是否和预期一致,如果不一致,从头debug找问题出在哪儿,没问题就提交测试,测试测出问题,再从头debug找问题出在哪儿。这个过程一般会比较费时,但一直以来都这么干,也没发现有什么问题。

后来看到一些安利单元测试的文章,被洗脑似的决定开始写单元测试。

然后,就没有然后了。

原来所有的代码逻辑都在Activity里,如何写单元测试?瞬间懵逼了。

很多公司或个人不愿意写单元测试的原因可能是觉得写单元测试并没用有什么卵用,项目比较赶根本没有时间写单元测试,不知道从何下手,特别是 Android 应用更是比较难写单元测试。

后来看到有文章说使用MVP模式可以方便的写单元测试,而且可以使用Junit写单元测试,直接运行在JVM上,而不需要运行在Android环境中。

然后就有了这篇文章 《如何将原项目重构成MVP模式》

开始实践

写了这么多铺垫,终于可以开始操刀写单元测试了,各位看官是不是已经急不可耐了。

就拿这个类开始写单元测试吧:

public class CreditCardPresenter extends BasePresenter<CreditCardContract.View, CreditCardContract.Model> implements CreditCardContract.Presenter {

    //其他代码略
    public void getCreditCards() {
        getModel().getCreditCards()
                .subscribe(new Subscriber<List<CreditCard>>(){
                    @Overridec
                    public void onNext(List<CreditCard> creditCards) {
                        getView().showCreditCards(creditCards);
                    }

                    @Override
                    public void onCompleted() {
                        getView().loadCompleted();
                    }

                    @Override
                    public void on
Error(Throwable e) {
                        getView().showError(e);
                    }

                });
    }

}

功能很简单,就是获取信用卡列表,如果获取成功就通过下面的代码显示:

getView().showCreditCards(creditCards);
getView().loadCompleted();

如果出错,则通知页面显示错误信息:

getView().showError(e);

又懵逼了,getModel() 和 getView() 里面还是会调用安卓的代码,怎么使用Junit做测试呢?

引入一个强大的测试框架:Mockito,接下来就可以开始使用Junit & Mockito做Java代码的单元测试了,这种方式的单元测试可以直接运行与JVM上,使用Mockito隔离Android相关代码。

然后就可以为CreditCardPresenter 写单元测试了, 为了方便,静态导入了Mockito的所有方法

import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class CreditCardPresenterTest {

    CreditCardPresenter creditCardPresenter;
    @Mock
    CreditCardContract.View creditCardView;
    @Mock
    CreditCardContract.Model creditCardModel;

    List<CreditCard> creditCards;

    @Before
    public void setUp() throws Exception {
        creditCardPresenter = new CreditCardPresenter();
        creditCardPresenter.attachView(creditCardView);
        creditCardPresenter.setModel(creditCardModel);
        creditCards = new ArrayList<>();
    }

    public void testGetCreditCards() {
        when(creditCardModel.getCreditCards()).thenReturn(Observable.create(new Observable.OnSubscribe<List<CreditCard>>() {
            @Override
            public void call(Subscriber<? super List<CreditCard>> subscriber) {
                subscriber.onNext(creditCards);
                subscriber.onCompleted();
            }
        }));

        creditCardPresenter.getCreditCards();

        verify(creditCardView).showCreditCards(creditCards);
        verify(creditCardView).loadCompleted();
    }

    public void testGetCreditCardson
Error() {
        final RuntimeException exception = new RuntimeException();
        when(creditCardModel.getCreditCards()).thenReturn(Observable.create(new Observable.OnSubscribe<List<CreditCard>>() {
            @Override
            public void call(Subscriber<? super List<CreditCard>> subscriber) {
                throw exception;
            }
        }));

        creditCardPresenter.getCreditCards();

        verify(creditCardView).showError(exception);
    }

}

这样就为上述两种情况写了两个单元测试。

其中使用@Mock注解来生成mock对象,也可以setUp方法中使用Mockito.mock()来生成mock对象,当使用注解的时候在类上必须加上注解@RunWith(MockitoJUnitRunner.class)

mock出来的对象的方法都是空实现,void方法声明也不做,有返回值的方法返回null(int 类型返回0,boolean类型返回false等)。

然后我们可以通过when(…).thenReturn(…)来为mock对象实现方法返回值。

when(creditCardModel.getCreditCards()).thenReturn(Observable.create(new Observable.OnSubscribe<List<CreditCard>>() {
            @Override
            public void call(Subscriber<? super List<CreditCard>> subscriber) {
                subscriber.onNext(creditCards);
                subscriber.onCompleted();
            }
        }));

上面代码的意思就是说当调用creditCardModel.getCreditCards()的时候返回值是:

Observable.create(new Observable.OnSubscribe<List<CreditCard>>() {
            @Override
            public void call(Subscriber<? super List<CreditCard>> subscriber) {
                subscriber.onNext(creditCards);
                subscriber.onCompleted();
            }
        })

最后使用verify()方法来校验某个方法是否被执行:

verify(creditCardView).showCreditCards(creditCards);

上面的代码意思就是说 creditCardView.showCreditCards(creditCards)方法被执行了,并且参数是creditCards,并且只执行了一次。如果有一个条件不符合就会报测试失败。

verify()还有很多重载方法,默认其实是这样的 verfy(creditCardView, times(1)).showCreditCards(creditCards); 校验只执行了一次,times(1) 可以传入不同的参数来校验方法被执行了几次。还可以替换了nerver(),表示某方法一次也不执行。

当然Mockito的功能远不止这么点,还有很多高级用法就不继续介绍了。

Mockito也有一些美中不足之处,不能mock静态方法,final方法等,比如项目中会有这样的方法 SelfApplication.getContext() 来获取自定义的Application,如果在测试代码中出现这类代码肯定会测试失败,因为JVM环境中没有Application,怎么办呢?

再引入一个配合Mockito使用的库:PowerMock

他弥补了Mockito的不足,可以mock静态方法和final方法,可以使用PowerMock来mock出SelfApplication.getContext(),从而不会调用到真正的Application对象:

PowerMockito.mockStatic(SelfApplication.class);
PowerMockito.when(SelfApplication.getContext()).thenReturn(mock(SelfApplication.class));

另外,在方法上要声明@PrepareForTest(SelfApplication.class), 在类上要声明  @RunWith(PowerMockRunner.class) 来支持上述mock。

这样当 调用SelfApplication.getContext()的时候将拿到一个mock对象,我们就可以继续使用when().thenReturn()方法来处理方法返回值了。

具体关于Mockito 和 PowerMock 的更多用法这里就不做过多介绍了,官网才是最好的教程。

这只是一个简单的例子,实际项目中会出现好的复杂的情况。这就要求我写的代码方法要短,耦合要低。写单元测试逼迫我们写更优雅的代码,也为我们下次修改需求或者重构代码提供了一道安全保障。还有其他更多的好处大家自己在实践中体会吧。

 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • expresso系列一简介
    expresso系列一简介
    Espresso 测试框架提供了一系列的API用于构建UI测试来测试app内用户流操作。这些API让你可以编写简洁可靠的自动化UI测试。Espresso非常适合用来编写白盒测试,其中测试代码的编写是利用了被测试app中程序代码实现细节。 Espresso测试可运行android 2.3.3(API
  • Android 测试 (四)-- 实战分析
    Android 测试 (四)-- 实战分析
    本文通过分析一个完整的项目,来学习如果对一个完整的工程比较好的进行编写测试程序 项目地址 ,该项目采用的是 mvp 架构,(关于 mvp 的介绍可以看这里 ),mvp 对于测试的好处就是讲 view 逻辑和业务代码分离,我们可以很方便的对业务代码进行 local unit t
  • Android Studio 2.2 中几个实用的新功能
    Android Studio 2.2 中几个实用的新功能
    Android Studio 2.2 preview 提供了几个新玩具,非常有用,如下:Espresso Test Recorder Dependencies管理 APK 分析器 查看 AndroidManifest.xml 的合并来源Espresso Test RecorderEspresso 是 UI 单元测试框架, Test Recorder 顾名思义就是用来录制 UI 单
  • 【Dev Club 分享第九期】安卓单元测试:What, Why and How
    【Dev Club 分享第九期】安卓单元测试:What, W
    Dev Club 是一个交流移动开发技术,结交朋友,扩展人脉的社群,成员都是经过审核的移动开发工程师。每周都会举行嘉宾分享,话题讨论等活动。本期,我们邀请了蘑菇街 Android 开发工程师——小创,为大家分享《安卓单元测试:What, Why and How》。分享内容简
  • Android学习笔记之应用单元测试实例分析
    Android学习笔记之应用单元测试实例分析
    这篇文章主要介绍了Android学习笔记之应用单元测试,结合实例形式较为详细的分析了Android单元测试的实现原理与具体步骤,具有一定参考借鉴价值,需要的朋友可以参考下
  • Android编程之单元测试实例分析
    Android编程之单元测试实例分析
    这篇文章主要介绍了Android编程之单元测试,结合具体实例分析了Android单元测试的具体实现步骤与相关注意事项,具有一定参考借鉴价值,需要的朋友可以参考下
  • Android编程单元测试实例详解(附源码)
    Android编程单元测试实例详解(附源码)
    这篇文章主要介绍了Android编程单元测试,结合完整实例形式详细分析了Android单元测试的具体步骤与相关技巧,并附带完整实例代码供读者下载参考,需要的朋友可以参考下
点击排行