转载

浅谈测试之Mockito

Mockito官网

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

mock怎么理解?“模拟”。

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

这时候,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实例。

只有mock实例,才能调用Mockito的API来mock各种方法。不然会抛各种异常。

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

浅谈测试之Mockito

使用方法如下:

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> )。)

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就好了。

浅谈测试之Mockito

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源码 里的测试用例。

原文  https://juejin.im/post/5acc761ef265da237d035676
正文到此结束
Loading...