Android 单元测试实践

若是想看更多文章能够到个人博客,博客会先更新:
Android 单元测试实践javascript

单元测试是什么

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

为何要作单元测试

卖个关子,看完文章天然就知道了复制代码

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

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

而后,就没有而后了。安全

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

不少公司或我的不肯意写单元测试的缘由多是以为写单元测试并没用有什么卵用,项目比较赶根本没有时间写单元测试,不知道从何下手,特别是 Android 应用更是比较难写单元测试。ide

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

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

开始实践

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

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

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 onError(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 testGetCreditCardsOnError() {
        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 的更多用法这里就不作过多介绍了,官网才是最好的教程。这只是一个简单的例子,实际项目中会出现好的复杂的状况。这就要求我写的代码方法要短,耦合要低。写单元测试逼迫咱们写更优雅的代码,也为咱们下次修改需求或者重构代码提供了一道安全保障。还有其余更多的好处你们本身在实践中体会吧。

相关文章
相关标签/搜索