浅谈测试之Mockito

Mockito的简介

Mockito官网java

Mockito是一个mock框架。可以帮助咱们使用更加简洁的API,写更漂亮、可读性更强的测试代码。android

mock怎么理解?“模拟”。git

就拿mvp架构来讲。当你想要测试presenter的某个方法时,若是在该方法里,有调用到model层的方法。 而你要根据model层这个方法的返回值,或者产生的反作用,来检测presenter里面的代码。github

这时候,Mockito就能派上用场了。你能够mock这个model的方法,或者返回任意你想要的值,或者让这个方法什么也不作(对无返回值的方法而言),甚至让这个方法抛各类异常,帮助你检测在各类不一样的状况下,你所写的方法,是否都能按你设计的步骤顺利执行。数组

Mockito的集成

1.app的build.gradle下添加依赖

testImplementation "org.mockito:mockito-core:2.27.0"
复制代码

Mockito的使用

1.mock和spy

mock(Class<T>)、spy(Class<T>)、spy(Object)均会生成一个mock实例。bash

只有mock实例,才能调用Mockito的API来mock各类方法。否则会抛各类异常。架构

注意,文中的“mock实例”,除非特别强调。通常都泛指经由mock(Class)、spy(Class)或spy(Object)方法产生的实例。记笔记~app

使用方法以下:

MockitoSample mockSample = mock(MockitoSample.class);
MockitoSample spySample = spy(MockitoSample.class);
MockitoSample spyRealSample = spy(new MockitoSample());
复制代码

上述操做中,mock(Class<T>)、spy(Class<T>)一般能够简化为下面的形式。但你要mock多个类的时候,较为简便:框架

@Mock
private MockitoSample mockSample;
@Spy
private MockitoSample spySample;

@Before
public void setup(){
    MockitoAnnotations.initMocks(this);
}
复制代码
1)spy(Class<T>)和spy(Object)

其中,由下面的部分Mockito源码可知,spy(Class<T>)、spy(Object)没有本质的区别。(因此,下文在介绍mock、spy区别的时候,为简便起见。只说spy(Class<T>)。)ide

public static <T> T spy(Class<T> classToSpy) {
    return MOCKITO_CORE.mock(classToSpy, withSettings()
            .useConstructor()//该方法返回一个MockSettings类的实例
            .defaultAnswer(CALLS_REAL_METHODS));
}

public static <T> T spy(T object) {
    return MOCKITO_CORE.mock((Class<T>) object.getClass(), withSettings()
            .spiedInstance(object)//该方法也是返回一个MockSettings类的实例
            .defaultAnswer(CALLS_REAL_METHODS));
}
复制代码
2)mock和spy的区别

mock(Class<T>)、spy(Class<T>)产生的实例,都能调用Mockito的API来mock方法。

经由mock(Class<T>)产生的实例,若是没有mock一个方法,就尝试经过该实例调用该方法,不会执行方法的真实逻辑,即不会执行任何操做。若是该方法有返回值,则Object类型、String类型、数组默认返回null,基本类型的数值类型默认返回0、boolean类型默认返回false,集合默认返回空集合,非null。

但经由spy(Class<T>)产生的实例,若是没有mock一个方法,就尝试经过该实例调用该方法,会执行方法真实逻辑。若是该方法有返回值,则返回真实逻辑执行后产生的值。

验证代码以下:

/**
 * publicMethodNoReturnThrowException是一个无返回值、会抛空指针异常的public方法。为了方便
 * 理解,文中给出的测试方法,都尽可能遵循这种命名。固然,项目实际运用不会这样命名。
 */
@Test
public void mockClass_notMockPublicMethodNoReturnThrowException(){
    mockSample.publicMethodNoReturnThrowException();
}

@Test(expected = NullPointerException.class)
public void spyClass_notMockPublicMethodNoReturnThrowException(){
    spySample.publicMethodNoReturnThrowException();
}
复制代码

对于上述知识点的更多验证代码,请参阅文末给出的测试demo。

2.when...thenReturn...和doReturn...when...

Mockito提供了不少相似when...thenReturn...或者doReturn...when...的方法,都是常见的mock一个方法的手段。

不少时候,这二者是能够互相通用的,你能够选择使用when...thenReturn...,也能够选择使用doReturn...when...。如:

String expected = "mockPublicMethodReturnString";
//注意,这里并无真的调用publicMethodReturnString()方法。该代码的含义是当mockSample调用
//publicMethodReturnString()方法时,将会返回你指望的值expected。
when(mockSample.publicMethodReturnString()).thenReturn(expected);
//跟前者等价
doReturn(expected).when(mockSample).publicMethodReturnString();
复制代码

when...thenReturn...doReturn...when...仍是有很多区别的:

1)when...thenReturn...更适合咱们的阅读习惯。而doReturn...when...有点反人类。

2)when...thenReturn...在mock一个方法时,能进行编译期类型检查。而doReturn...when...不行。但这并非多重要的特性,由于单元测试运行速度快,doReturn...when...一运行也能立马检测出来错误。

//传入错误的返回值类型int,编译器将会报错
when(mockSample.publicMethodReturnString()).thenReturn(1);
//传入错误的返回值类型int,编译器不会报错,只有在运行时才能检测到错误
doReturn(1).when(mockSample).publicMethodReturnString();
复制代码

3)when...thenReturn...没法mock返回值为void的方法。而doReturn...when...能够。

//publicMethodNoReturnThrowException是一个返回值为void,会抛空指针异常的方法。
//when...thenReturn...没有对应的方法。也不能mock返回值为void的方法。
doNothing().when(mockSample).publicMethodNoReturnThrowException();
//when...thenReturn...有对应的方法。但也不能mock返回值为void的方法。
doThrow(IllegalArgumentException.class).when(mockSample).publicMethodNoReturnThrowException();
复制代码

4)mock、spy产生的mock实例,使用这二者mock方法时,有时会产生不一样的行为:

经由mock(Class<T>)产生的实例,经过when...thenReturn...来mock一个方法。而后用该mock实例调用该方法时,在返回指定值以前,不会走真实逻辑。

经由mock(Class<T>)产生的实例,经过doReturn...when...来mock一个方法。而后用该mock实例调用该方法时,在返回指定值以前,不会走真实逻辑。

经由spy(Class<T>)产生的实例,经过when...thenReturn...来mock一个方法。而后用该mock实例调用该方法时,在返回指定值以前,走真实逻辑。(这里使用时,须要特别注意。由于这一点,在使用spy(Class<T>)产生的实例来mock方法的时候,我的不推荐使用when...thenReturn...,最好使用doReturn...when...)

经由spy(Class<T>)产生的实例,经过doReturn...when...来mock一个方法。而后用该mock实例调用该方法时,在返回指定值以前,不会走真实逻辑。

验证代码,请参阅文末给出的测试demo。

参考资料:Mockito - difference between doReturn() and when()

3.其余常见的Mockito的API

1)verify

主要用于验证一个方法被调用过多少次。使用示例代码:

@Test
public void verify_publicMethodReturnString() {
    verify(mockSample, never()).publicMethodReturnString();

    mockSample.publicMethodReturnString();

    //默认状况下是times(1)。times(1)能够被省略。
    verify(mockSample).publicMethodReturnString();
     mockSample.publicMethodReturnString();
    verify(mockSample, times(2)).publicMethodReturnString();
}
复制代码
2)参数匹配器isA、anyXxx、eq

主要用于配合verify方法,验证方法的调用。使用示例代码:

@Test
public void verify_publicMethodCalculate() {
    //能够不使用参数匹配器。
    verify(mockSample, never()).publicMethodCalculate(1, 2);

    //若是你使用参数匹配器(isA(Class<T>)、anyXxx()、eq()),全部的参数都必须由匹配器提供。。
    verify(mockSample, never()).publicMethodCalculate(isA(int.class), isA(int.class));
    verify(mockSample, never()).publicMethodCalculate(anyInt(), anyInt());
    verify(mockSample, never()).publicMethodCalculate(eq(1), eq(2));

    mockSample.publicMethodCalculate(1, 2);
    verify(mockSample).publicMethodCalculate(1, 2);
    verify(mockSample).publicMethodCalculate(isA(int.class), isA(int.class));
    verify(mockSample).publicMethodCalculate(anyInt(), anyInt());
    verify(mockSample).publicMethodCalculate(eq(1), eq(2));
    verify(mockSample, never()).publicMethodCalculate(1, 1);
    verify(mockSample, never()).publicMethodCalculate(eq(1), eq(1));
}
复制代码

Mockito的实战

看前面看得一脸懵逼?不要紧,来实战一下吧~

下面演示如何测试常见的mvp代码presenter里面的一个loadData()方法。

该Presenter:

public class MainPresenter implements MainContract.Presenter {
    private MainContract.View view;
    private TestDataSource testDataSource;

    public MainPresenter(TestDataSource testDataSource) {
        this.testDataSource = testDataSource;
    }

    @Override 
    public void attachView(MainContract.View view) {
        this.view = view;
    }

    @Override 
    public void loadData() {
        getDataAndHandle();
    }

    private void getDataAndHandle() {
        //省略一大堆的处理逻辑......
        testDataSource.getData()(new TestDataSource.GetDataCallback() {
            @Override 
            public void onSuccess(List<Person> peoples) {
                //省略一大堆的处理逻辑......
                if (view != null) view.showPersons(peoples);
            }

            @Override 
            public void onError() {
                //省略一大堆的处理逻辑......
                if (view != null) view.showNotDataToast();
            }
        });
    }
}
复制代码

注意:

为了便于咱们测试,presenter的设计也很讲究。这里使用到了依赖注入的思想。model层的TestDataSource的实例,是在presenter的构造方法调用以前就建立好,再传进presenter里面的。这就是一个最简单的依赖注入实现。而Dagger2这些依赖注入框架,只是简化咱们手动一个个去new要注入的实例的繁琐步骤。

为何要使用依赖注入?当咱们要测试loadData()方法时,咱们要使用Mockito控制testDataSource的getData(TestDataSource.GetDataCallback)方法,按照咱们的意愿返回。但前面也说了,“只有mock实例,才能调用Mockito的API来mock各类方法”。若是你的TestDataSource实例是在presenter的构造方法里面建立的。那么你怎么用你的mock实例替换它?诚然,你能够暴露一对set/get方法,用来替换原来代码中的testDataSource,但该set/get方法仅是为了测试而妥协,并无别的实际用处。若是你有多个要mock的类,那岂不是要多写一堆set/get方法?

但若是你使用依赖注入,就能够避免这种尴尬。若是咱们一开始,传入presenter的就是一个mock实例,那么一切迎刃而解。

还有,须要注意的是,若是你使用了Dagger2,在写测试代码时,不建议使用Dagger2建立这个Presenter。直接像下面代码同样,new一个Presenter就行了。

presenter代码:

private void getDataAndHandle() {
    testRepository.getData(new GetDataCallback() {
        @Override
        public void onSuccess(List<Person> peoples) {
            if (view != null) view.showPersons(peoples);
        }

        @Override
        public void onError() {
            if (view != null) view.showNotDataToast();
        }
    });
}
复制代码

测试代码:

public class MainPresenterTest {

    @Mock 
    private TestDataSource testDataSource;
    @Mock 
    private MainContract.View view;
    private MainPresenter mainPresenter;
    /**
     * {@link ArgumentCaptor}Captor是一个功能强大的Mockito API,用于捕获参数值并使用它们,
     * 对它们进行进一步的行动或断言。但我在本身的项目里,相比回调,更多的时候,用的都是
     * RxJava来获取model层的数据。好处是,不用声明各类回调接口,并且RxJava在设计的时候,就考
     * 虑到了测试的问题,更易于写测试代码。
     */
    @Captor 
    private ArgumentCaptor<TestDataSource.GetDataCallback> getDataCallbackCaptor;
    private List<Person> peoples;

    @Before 
    public void setup() {
        //快速mock多个类
        MockitoAnnotations.initMocks(this);
        //注意,传入的是一个已经经由mock(Class`<T>`)产生的实例
        mainPresenter = new MainPresenter(testDataSource);
        mainPresenter.attachView(view);
        peoples = Arrays.asList(new Person("Tony"), new Person("Alice"));
    }

    @Test 
    public void loadData() {
        mainPresenter.loadData();
        verify(testDataSource, times(1)).getData(getDataCallbackCaptor.capture());

        //验证获取数据成功后的逻辑是否顺利完成
        getDataCallbackCaptor.getValue().onSuccess(peoples);
        verify(view, times(1)).showPersons(peoples);

        //验证获取数据失败后的逻辑是否顺利完成
        getDataCallbackCaptor.getValue().onError();
        verify(view, times(1)).showNotDataToast();
    }
}
复制代码

更多的测试Presenter的示例代码,能够参考谷歌的android-architecture几个分支里面的测试代码,好比:

1)todo-mvp:presenter层使用回调获取model层的数据。

2)todo-mvp-rxjava:presenter层使用rxjava获取model层的数据两个分支。

该选择mock,仍是spy?

mock、spy在实际运用时,该作何选择?

简单归纳为:

1.要mock其余类的方法

这时使用mock(Class<T>)。好比,测试presenter,咱们要mock掉model层的方法,通常都是使用mock(Class<T>)。但有人会说,若是我只想mock掉model层的部分方法,一些方法仍是让它走真实逻辑呢?通常来讲,不会这样子作,毕竟咱们如今要测试的是presenter的方法,应该排除model的干扰,mock掉model层的方法。

2.要mock自身的成员方法

这时使用spy(Class<T>)。好比,现实项目中,我有一个Printer类,大体代码以下(固然,项目里的代码比这还复杂得多):

public void print(String filePath, Callback callback) {
    if (getFormat(filePath).equals("pdf")) {
        String newFilePath = transform(filePath);
        jumpToPrinterShare(newFilePath, callback);
    } else {
        jumpToPrinterShare(filePath, callback);
    }
}
复制代码

当我想测试print方法在传入不一样类型的文件时,可否顺利跳转到PrinterShare,遇到了点小问题。 因为打印机软件PrinterShare对含中文字符的pdf的渲染很差,因此要用第三方框架,把pdf转成图片再打印,也就是这个transform方法。因为它过于复杂,又涉及到第三方库,可能会影响到咱们的测试,因此须要mock该方法。这时,就须要使用spy。注意,这里使用spy(Class<T>)时,传入的是Printer这个类。而后使用mock实例,调用Mockito的相应API来mock掉transform方法。最后用mock实例,直接调用print方法。这样print方法会走真实逻辑,但若是执行到调用transform方法的地方,不会真的执行此方法,而是直接用你mock的值,继续往下执行剩余逻辑。

测试代码:

public class PrinterTest {
    @Spy
    private Printer printer;
    @Mock
    Callback callback;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testPrinterPDFSuccess() {
        doReturn("D:\\demo\\a.jpg").when(printer).transform(anyString());
        printer.print("D:\\demo\\a.pdf", callback);
        verify(callback).onSucess("jpg");
    }
}
复制代码

后记

Mockito主要用于单元测试上。使用时,也须要注意一下代码的设计结构,方便测试。

另外,Mockito是不能mock私有方法、静态方法的。2.1.0版本之前的Mockito是不能mock final类和final方法的,以后的也要经过配置一些相关文件才行(Mock the unmockable: opt-in mocking of final classes/methods)。所以,它的补充框架PowerMock也应运而生。(有时候,2.1.0之后的Mockito,采用上述配置文件也未必能mock final类和final方法,跟你的java版本有关)

文中的相关测试例子,以及更多的测试例子都可以在UnitTest里面找到。

更多的测试例子,以及相关API的使用方法,请参考Mockito源码里的测试用例。

相关文章
相关标签/搜索