java如何使用Mockito?


https://github.com/xitu/gold-miner/blob/master/TODO/Unit-tests-with-Mockito.md

這篇教程介紹了如何使用 Mockito 框架來給軟件寫測試用例

1. 預備知識

如果需要往下學習,你需要先理解 Junit 框架中的單元測試。

如果你不熟悉 JUnit,請查看下面的教程: http://www.vogella.com/tutorials/JUnit/article.html

2. 使用mock對象來進行測試

2.1. 單元測試的目標和挑戰

單元測試的思路是在不涉及依賴關系的情況下測試代碼(隔離性),所以測試代碼與其他類或者系統的關系應該盡量被消除。一個可行的消除方法是替換掉依賴類(測試替換),也就是說我們可以使用替身來替換掉真正的依賴對象。

2.2. 測試類的分類

dummy object 做為參數傳遞給方法但是絕對不會被使用。譬如說,這種測試類內部的方法不會被調用,或者是用來填充某個方法的參數。

Fake 是真正接口或抽象類的實現體,但給對象內部實現很簡單。譬如說,它存在內存中而不是真正的數據庫中。(譯者注:Fake 實現了真正的邏輯,但它的存在只是為了測試,而不適合於用在產品中。)

stub 類是依賴類的部分方法實現,而這些方法在你測試類和接口的時候會被用到,也就是說 stub 類在測試中會被實例化。stub 類會回應任何外部測試的調用。stub 類有時候還會記錄調用的一些信息。

mock object 是指類或者接口的模擬實現,你可以自定義這個對象中某個方法的輸出結果。

測試替代技術能夠在測試中模擬測試類以外對象。因此你可以驗證測試類是否響應正常。譬如說,你可以驗證在 Mock 對象的某一個方法是否被調用。這可以確保隔離了外部依賴的干擾只測試測試類。

我們選擇 Mock 對象的原因是因為 Mock 對象只需要少量代碼的配置。

2.3. Mock 對象的產生

你可以手動創建一個 Mock 對象或者使用 Mock 框架來模擬這些類,Mock 框架允許你在運行時創建 Mock 對象並且定義它的行為。

一個典型的例子是把 Mock 對象模擬成數據的提供者。在正式的生產環境中它會被實現用來連接數據源。但是我們在測試的時候 Mock 對象將會模擬成數據提供者來確保我們的測試環境始終是相同的。

Mock 對象可以被提供來進行測試。因此,我們測試的類應該避免任何外部數據的強依賴。

通過 Mock 對象或者 Mock 框架,我們可以測試代碼中期望的行為。譬如說,驗證只有某個存在 Mock 對象的方法是否被調用了。

2.4. 使用 Mockito 生成 Mock 對象

Mockito 是一個流行 mock 框架,可以和JUnit結合起來使用。Mockito 允許你創建和配置 mock 對象。使用Mockito可以明顯的簡化對外部依賴的測試類的開發。

一般使用 Mockito 需要執行下面三步

  • 模擬並替換測試代碼中外部依賴。

  • 執行測試代碼

  • 驗證測試代碼是否被正確的執行

mockitousagevisualization

3. 為自己的項目添加 Mockito 依賴

3.1. 在 Gradle 添加 Mockito 依賴

如果你的項目使用 Gradle 構建,將下面代碼加入 Gradle 的構建文件中為自己項目添加 Mockito 依賴

  1.  
    repositories { jcenter() }
  2.  
    dependencies { testCompile "org.mockito:mockito-core:2.0.57-beta" }

3.2. 在 Maven 添加 Mockito 依賴

需要在 Maven 聲明依賴,您可以在 http://search.maven.org 網站中搜索 g:"org.mockito", a:"mockito-core" 來得到具體的聲明方式。

3.3. 在 Eclipse IDE 使用 Mockito

Eclipse IDE 支持 Gradle 和 Maven 兩種構建工具,所以在 Eclipse IDE 添加依賴取決你使用的是哪一個構建工具。

3.4. 以 OSGi 或者 Eclipse 插件形式添加 Mockito 依賴

在 Eclipse RCP 應用依賴通常可以在 p2 update 上得到。Orbit 是一個很好的第三方倉庫,我們可以在里面尋找能在 Eclipse 上使用的應用和插件。

Orbit 倉庫地址 http://download.eclipse.org/tools/orbit/downloads

orbit p2 mockito

4. 使用Mockito API

4.1. 靜態引用

如果在代碼中靜態引用了org.mockito.Mockito.*;,那你你就可以直接調用靜態方法和靜態變量而不用創建對象,譬如直接調用 mock() 方法。

4.2. 使用 Mockito 創建和配置 mock 對象

除了上面所說的使用 mock() 靜態方法外,Mockito 還支持通過 @Mock 注解的方式來創建 mock 對象。

如果你使用注解,那么必須要實例化 mock 對象。Mockito 在遇到使用注解的字段的時候,會調用MockitoAnnotations.initMocks(this) 來初始化該 mock 對象。另外也可以通過使用@RunWith(MockitoJUnitRunner.class)來達到相同的效果。

通過下面的例子我們可以了解到使用@Mock 的方法和MockitoRule規則。

  1.  
    import static org.mockito.Mockito.*;
  2.  
     
  3.  
    public class MockitoTest {
  4.  
     
  5.  
    @Mock
  6.  
    MyDatabase databaseMock; ( 1)
  7.  
     
  8.  
    @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); (2)
  9.  
     
  10.  
    @Test
  11.  
    public void testQuery() {
  12.  
    ClassToTest t = new ClassToTest(databaseMock); (3)
  13.  
    boolean check = t.query("* from t"); (4)
  14.  
    assertTrue(check); ( 5)
  15.  
    verify(databaseMock).query( "* from t"); (6)
  16.  
    }
  17.  
    }
  1. 告訴 Mockito 模擬 databaseMock 實例

  2. Mockito 通過 @mock 注解創建 mock 對象

  3. 使用已經創建的mock初始化這個類

  4. 在測試環境下,執行測試類中的代碼

  5. 使用斷言確保調用的方法返回值為 true

  6. 驗證 query 方法是否被 MyDatabase 的 mock 對象調用

4.3. 配置 mock

當我們需要配置某個方法的返回值的時候,Mockito 提供了鏈式的 API 供我們方便的調用

when(…​.).thenReturn(…​.)可以被用來定義當條件滿足時函數的返回值,如果你需要定義多個返回值,可以多次定義。當你多次調用函數的時候,Mockito 會根據你定義的先后順序來返回返回值。Mocks 還可以根據傳入參數的不同來定義不同的返回值。譬如說你的函數可以將anyString 或者 anyInt作為輸入參數,然后定義其特定的放回值。

  1.  
    import static org.mockito.Mockito.*;
  2.  
    import static org.junit.Assert.*;
  3.  
     
  4.  
    @Test
  5.  
    public void test1() {
  6.  
    // 創建 mock
  7.  
    MyClass test = Mockito.mock(MyClass.class);
  8.  
     
  9.  
    // 自定義 getUniqueId() 的返回值
  10.  
    when(test.getUniqueId()).thenReturn( 43);
  11.  
     
  12.  
    // 在測試中使用mock對象
  13.  
    assertEquals(test.getUniqueId(), 43);
  14.  
    }
  15.  
     
  16.  
    // 返回多個值
  17.  
    @Test
  18.  
    public void testMoreThanOneReturnValue() {
  19.  
    Iterator i= mock(Iterator.class);
  20.  
    when(i.next()).thenReturn( "Mockito").thenReturn("rocks");
  21.  
    String result=i.next()+ " "+i.next();
  22.  
    // 斷言
  23.  
    assertEquals( "Mockito rocks", result);
  24.  
    }
  25.  
     
  26.  
    // 如何根據輸入來返回值
  27.  
    @Test
  28.  
    public void testReturnValueDependentOnMethodParameter() {
  29.  
    Comparable c= mock(Comparable.class);
  30.  
    when(c.compareTo( "Mockito")).thenReturn(1);
  31.  
    when(c.compareTo( "Eclipse")).thenReturn(2);
  32.  
    // 斷言
  33.  
    assertEquals( 1,c.compareTo("Mockito"));
  34.  
    }
  35.  
     
  36.  
    // 如何讓返回值不依賴於輸入
  37.  
    @Test
  38.  
    public void testReturnValueInDependentOnMethodParameter() {
  39.  
    Comparable c= mock(Comparable.class);
  40.  
    when(c.compareTo(anyInt())).thenReturn(- 1);
  41.  
    // 斷言
  42.  
    assertEquals(- 1 ,c.compareTo(9));
  43.  
    }
  44.  
     
  45.  
    // 根據參數類型來返回值
  46.  
    @Test
  47.  
    public void testReturnValueInDependentOnMethodParameter() {
  48.  
    Comparable c= mock(Comparable.class);
  49.  
    when(c.compareTo(isA(Todo.class))).thenReturn( 0);
  50.  
    // 斷言
  51.  
    Todo todo = new Todo(5);
  52.  
    assertEquals(todo ,c.compareTo( new Todo(1)));
  53.  
    }

對於無返回值的函數,我們可以使用doReturn(…​).when(…​).methodCall來獲得類似的效果。例如我們想在調用某些無返回值函數的時候拋出異常,那么可以使用doThrow 方法。如下面代碼片段所示

  1.  
    import static org.mockito.Mockito.*;
  2.  
    import static org.junit.Assert.*;
  3.  
     
  4.  
    // 下面測試用例描述了如何使用doThrow()方法
  5.  
     
  6.  
    @Test(expected=IOException.class)
  7.  
    public void testForIOException() {
  8.  
    // 創建並配置 mock 對象
  9.  
    OutputStream mockStream = mock(OutputStream.class);
  10.  
    doThrow( new IOException()).when(mockStream).close();
  11.  
     
  12.  
    // 使用 mock
  13.  
    OutputStreamWriter streamWriter= new OutputStreamWriter(mockStream);
  14.  
    streamWriter.close();
  15.  
    }

4.4. 驗證 mock 對象方法是否被調用

Mockito 會跟蹤 mock 對象里面所有的方法和變量。所以我們可以用來驗證函數在傳入特定參數的時候是否被調用。這種方式的測試稱行為測試,行為測試並不會檢查函數的返回值,而是檢查在傳入正確參數時候函數是否被調用。

  1.  
    import static org.mockito.Mockito.*;
  2.  
     
  3.  
    @Test
  4.  
    public void testVerify() {
  5.  
    // 創建並配置 mock 對象
  6.  
    MyClass test = Mockito.mock(MyClass.class);
  7.  
    when(test.getUniqueId()).thenReturn( 43);
  8.  
     
  9.  
    // 調用mock對象里面的方法並傳入參數為12
  10.  
    test.testing( 12);
  11.  
    test.getUniqueId();
  12.  
    test.getUniqueId();
  13.  
     
  14.  
    // 查看在傳入參數為12的時候方法是否被調用
  15.  
    verify(test).testing(Matchers.eq( 12));
  16.  
     
  17.  
    // 方法是否被調用兩次
  18.  
    verify(test, times( 2)).getUniqueId();
  19.  
     
  20.  
    // 其他用來驗證函數是否被調用的方法
  21.  
    verify(mock, never()).someMethod( "never called");
  22.  
    verify(mock, atLeastOnce()).someMethod( "called at least once");
  23.  
    verify(mock, atLeast( 2)).someMethod("called at least twice");
  24.  
    verify(mock, times( 5)).someMethod("called five times");
  25.  
    verify(mock, atMost( 3)).someMethod("called at most 3 times");
  26.  
    }

4.5. 使用 Spy 封裝 java 對象

@Spy或者spy()方法可以被用來封裝 java 對象。被封裝后,除非特殊聲明(打樁 stub),否則都會真正的調用對象里面的每一個方法

  1.  
    import static org.mockito.Mockito.*;
  2.  
     
  3.  
    // Lets mock a LinkedList
  4.  
    List list = new LinkedList();
  5.  
    List spy = spy(list);
  6.  
     
  7.  
    // 可用 doReturn() 來打樁
  8.  
    doReturn( "foo").when(spy).get(0);
  9.  
     
  10.  
    // 下面代碼不生效
  11.  
    // 真正的方法會被調用
  12.  
    // 將會拋出 IndexOutOfBoundsException 的異常,因為 List 為空
  13.  
    when(spy.get( 0)).thenReturn("foo");

方法verifyNoMoreInteractions()允許你檢查沒有其他的方法被調用了。

4.6. 使用 @InjectMocks 在 Mockito 中進行依賴注入

我們也可以使用@InjectMocks 注解來創建對象,它會根據類型來注入對象里面的成員方法和變量。假定我們有 ArticleManager 類

  1.  
    public class ArticleManager {
  2.  
    private User user;
  3.  
    private ArticleDatabase database;
  4.  
     
  5.  
    ArticleManager(User user) {
  6.  
    this.user = user;
  7.  
    }
  8.  
     
  9.  
    void setDatabase(ArticleDatabase database) { }
  10.  
    }

這個類會被 Mockito 構造,而類的成員方法和變量都會被 mock 對象所代替,正如下面的代碼片段所示:

  1.  
    @RunWith(MockitoJUnitRunner.class)
  2.  
    public class ArticleManagerTest {
  3.  
     
  4.  
    @Mock ArticleCalculator calculator;
  5.  
    @Mock ArticleDatabase database;
  6.  
    @Most User user;
  7.  
     
  8.  
    @Spy private UserProvider userProvider = new ConsumerUserProvider();
  9.  
     
  10.  
    @InjectMocks private ArticleManager manager; (1)
  11.  
     
  12.  
    @Test public void shouldDoSomething() {
  13.  
    // 假定 ArticleManager 有一個叫 initialize() 的方法被調用了
  14.  
    // 使用 ArticleListener 來調用 addListener 方法
  15.  
    manager.initialize();
  16.  
     
  17.  
    // 驗證 addListener 方法被調用
  18.  
    verify(database).addListener(any(ArticleListener.class));
  19.  
    }
  20.  
    }
  1. 創建ArticleManager實例並注入Mock對象

更多的詳情可以查看 http://docs.mockito.googlecode.com/hg/1.9.5/org/mockito/InjectMocks.html.

4.7. 捕捉參數

ArgumentCaptor類允許我們在verification期間訪問方法的參數。得到方法的參數后我們可以使用它進行測試。

  1.  
    import static org.hamcrest.Matchers.hasItem;
  2.  
    import static org.junit.Assert.assertThat;
  3.  
    import static org.mockito.Mockito.mock;
  4.  
    import static org.mockito.Mockito.verify;
  5.  
     
  6.  
    import java.util.Arrays;
  7.  
    import java.util.List;
  8.  
     
  9.  
    import org.junit.Rule;
  10.  
    import org.junit.Test;
  11.  
    import org.mockito.ArgumentCaptor;
  12.  
    import org.mockito.Captor;
  13.  
    import org.mockito.junit.MockitoJUnit;
  14.  
    import org.mockito.junit.MockitoRule;
  15.  
     
  16.  
    public class MockitoTests {
  17.  
     
  18.  
    @ Rule public MockitoRule rule = MockitoJUnit.rule();
  19.  
     
  20.  
    @ Captor
  21.  
    private ArgumentCaptor> captor;
  22.  
     
  23.  
    @ Test
  24.  
    public final void shouldContainCertainListItem() {
  25.  
    List asList = Arrays.asList("someElement_test", "someElement");
  26.  
    final List mockedList = mock(List.class);
  27.  
    mockedList.addAll(asList);
  28.  
     
  29.  
    verify(mockedList).addAll(captor.capture());
  30.  
    final List capturedArgument = captor.>getValue();
  31.  
    assertThat(capturedArgument, hasItem( "someElement"));
  32.  
    }
  33.  
    }

4.8. Mockito的限制

Mockito當然也有一定的限制。而下面三種數據類型則不能夠被測試

  • final classes

  • anonymous classes

  • primitive types

5. 在Android中使用Mockito

在 Android 中的 Gradle 構建文件中加入 Mockito 依賴后就可以直接使用 Mockito 了。若想使用 Android Instrumented tests 的話,還需要添加 dexmaker 和 dexmaker-mockito 依賴到 Gradle 的構建文件中。(需要 Mockito 1.9.5版本以上)

  1.  
    dependencies {
  2.  
    testCompile 'junit:junit:4.12'
  3.  
    // Mockito unit test 的依賴
  4.  
    testCompile 'org.mockito:mockito-core:1.+'
  5.  
    // Mockito Android instrumentation tests 的依賴
  6.  
    androidTestCompile 'org.mockito:mockito-core:1.+'
  7.  
    androidTestCompile "com.google.dexmaker:dexmaker:1.2"
  8.  
    androidTestCompile "com.google.dexmaker:dexmaker-mockito:1.2"
  9.  
    }

6. 實例:使用Mockito寫一個Instrumented Unit Test

6.1. 創建一個測試的Android 應用

創建一個包名為com.vogella.android.testing.mockito.contextmock的Android應用,添加一個靜態方法 ,方法里面創建一個包含參數的Intent,如下代碼所示:

  1.  
    public static Intent createQuery(Context context, String query, String value) {
  2.  
    // 簡單起見,重用MainActivity
  3.  
    Intent i = new Intent(context, MainActivity.class);
  4.  
    i.putExtra( "QUERY", query);
  5.  
    i.putExtra( "VALUE", value);
  6.  
    return i;
  7.  
    }

6.2. 在app/build.gradle文件中添加Mockito依賴

  1.  
    dependencies {
  2.  
    // Mockito 和 JUnit 的依賴
  3.  
    // instrumentation unit tests on the JVM
  4.  
    androidTestCompile 'junit:junit:4.12'
  5.  
    androidTestCompile 'org.mockito:mockito-core:2.0.57-beta'
  6.  
    androidTestCompile 'com.android.support.test:runner:0.3'
  7.  
    androidTestCompile "com.google.dexmaker:dexmaker:1.2"
  8.  
    androidTestCompile "com.google.dexmaker:dexmaker-mockito:1.2"
  9.  
     
  10.  
    // Mockito 和 JUnit 的依賴
  11.  
    // tests on the JVM
  12.  
    testCompile 'junit:junit:4.12'
  13.  
    testCompile 'org.mockito:mockito-core:1.+'
  14.  
     
  15.  
    }

6.3. 創建測試

使用 Mockito 創建一個單元測試來驗證在傳遞正確 extra data 的情況下,intent 是否被觸發。

因此我們需要使用 Mockito 來 mock 一個Context對象,如下代碼所示:

  1.  
    package com.vogella.android.testing.mockitocontextmock;
  2.  
     
  3.  
    import android.content.Context;
  4.  
    import android.content.Intent;
  5.  
    import android.os.Bundle;
  6.  
     
  7.  
    import org.junit.Test;
  8.  
    import org.junit.runner.RunWith;
  9.  
    import org.mockito.Mockito;
  10.  
     
  11.  
    import static org.junit.Assert.assertEquals;
  12.  
    import static org.junit.Assert.assertNotNull;
  13.  
     
  14.  
    public class TextIntentCreation {
  15.  
     
  16.  
    @ Test
  17.  
    public void testIntentShouldBeCreated() {
  18.  
    Context context = Mockito.mock(Context.class);
  19.  
    Intent intent = MainActivity.createQuery(context, "query", "value");
  20.  
    assertNotNull(intent);
  21.  
    Bundle extras = intent.getExtras();
  22.  
    assertNotNull(extras);
  23.  
    assertEquals( "query", extras.getString("QUERY"));
  24.  
    assertEquals( "value", extras.getString("VALUE"));
  25.  
    }
  26.  
    }

7. 實例:使用 Mockito 創建一個 mock 對象

7.1. 目標

創建一個 Api,它可以被 Mockito 來模擬並做一些工作

7.2. 創建一個Twitter API 的例子

實現 TwitterClient類,它內部使用到了 ITweet 的實現。但是ITweet實例很難得到,譬如說他需要啟動一個很復雜的服務來得到。

  1.  
    public interface ITweet {
  2.  
     
  3.  
    String getMessage();
  4.  
    }
  5.  
     
  6.  
     
  7.  
    public class TwitterClient {
  8.  
     
  9.  
    public void sendTweet(ITweet tweet) {
  10.  
    String message = tweet.getMessage();
  11.  
     
  12.  
    // send the message to Twitter
  13.  
    }
  14.  
    }

7.3. 模擬 ITweet 的實例

為了能夠不啟動復雜的服務來得到 ITweet,我們可以使用 Mockito 來模擬得到該實例。

  1.  
    @Test
  2.  
    public void testSendingTweet() {
  3.  
    TwitterClient twitterClient = new TwitterClient();
  4.  
     
  5.  
    ITweet iTweet = mock(ITweet.class);
  6.  
     
  7.  
    when(iTweet.getMessage()).thenReturn( "Using mockito is great");
  8.  
     
  9.  
    twitterClient.sendTweet(iTweet);
  10.  
    }

現在 TwitterClient 可以使用 ITweet 接口的實現,當調用 getMessage() 方法的時候將會打印 "Using Mockito is great" 信息。

7.4. 驗證方法調用

確保 getMessage() 方法至少調用一次。

  1.  
    @Test
  2.  
    public void testSendingTweet() {
  3.  
    TwitterClient twitterClient = new TwitterClient();
  4.  
     
  5.  
    ITweet iTweet = mock(ITweet.class);
  6.  
     
  7.  
    when(iTweet.getMessage()).thenReturn( "Using mockito is great");
  8.  
     
  9.  
    twitterClient.sendTweet(iTweet);
  10.  
     
  11.  
    verify(iTweet, atLeastOnce()).getMessage();
  12.  
    }

7.5. 驗證

運行測試,查看代碼是否測試通過。

8. 模擬靜態方法

8.1. 使用 Powermock 來模擬靜態方法

因為 Mockito 不能夠 mock 靜態方法,因此我們可以使用 Powermock

  1.  
    import java.net.InetAddress;
  2.  
    import java.net.UnknownHostException;
  3.  
     
  4.  
    public final class NetworkReader {
  5.  
    public static String getLocalHostname() {
  6.  
    String hostname = "";
  7.  
    try {
  8.  
    InetAddress addr = InetAddress.getLocalHost();
  9.  
    // Get hostname
  10.  
    hostname = addr.getHostName();
  11.  
    } catch ( UnknownHostException e ) {
  12.  
    }
  13.  
    return hostname;
  14.  
    }
  15.  
    }

我們模擬了 NetworkReader 的依賴,如下代碼所示:

  1.  
    import org.junit.runner.RunWith;
  2.  
    import org.powermock.core.classloader.annotations.PrepareForTest;
  3.  
     
  4.  
    @RunWith( PowerMockRunner.class )
  5.  
    @PrepareForTest( NetworkReader.class )
  6.  
    public class MyTest {
  7.  
     
  8.  
    // 測試代碼
  9.  
     
  10.  
    @Test
  11.  
    public void testSomething() {
  12.  
    mockStatic( NetworkUtil. class );
  13.  
    when( NetworkReader.getLocalHostname() ).andReturn( "localhost" );
  14.  
     
  15.  
    // 與 NetworkReader 協作的測試
  16.  
    }

8.2.用封裝的方法代替Powermock

有時候我們可以在靜態方法周圍包含非靜態的方法來達到和 Powermock 同樣的效果。

  1.  
    class FooWraper {
  2.  
    void someMethod() {
  3.  
    Foo.someStaticMethod()
  4.  
    }
  5.  
    }

9. Mockito 參考資料

http://site.mockito.org - Mockito 官網

https://github.com/mockito/mockito- Mockito Github

https://github.com/mockito/mockito/blob/master/doc/release-notes/official.md - Mockito 發行說明

http://martinfowler.com/articles/mocksArentStubs.html 與Mocks,Stub有關的文章

http://chiuki.github.io/advanced-android-espresso/ 高級android教程(竟然是個妹子)

 

from: https://github.com/xitu/gold-miner/blob/master/TODO/Unit-tests-with-Mockito.md


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM