淺談測試之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源碼裏的測試用例。

相關文章
相關標籤/搜索