單元測試系列之五:Mock工具之Mockito實戰

更多原創測試技術文章同步更新到微信公衆號 :三國測,敬請掃碼關注我的的微信號,感謝!html

原文連接:http://www.cnblogs.com/zishi/p/6780719.htmljava

在實際項目中寫單元測試的過程當中咱們會發現須要測試的類有不少依賴,這些依賴項又會有依賴,致使在單元測試代碼裏幾乎沒法完成構建,尤爲是當依賴項還沒有構建完成時會致使單元測試沒法進行。爲了解決這類問題咱們引入了Mock的概念,簡單的說就是模擬這些須要構建的類或者資源,提供給須要測試的對象使用。業內的Mock工具備不少,也已經很成熟了,這裏咱們將直接使用最流行的Mockito進行實戰演練,完成mockito教程。git

Mock工具概述

1.1  Mockito簡介

 

EasyMock 以及 Mockito 都由於能夠極大地簡化單元測試的書寫過程而被許多人應用在本身的工做中,可是這兩種 Mock 工具都不能夠實現對靜態函數、構造函數、私有函數、Final 函數以及系統函數的模擬,可是這些方法每每是咱們在大型系統中須要的功能。github

另外,關於更多Mockito2.0新特性,參考官方介紹文檔,裏邊有關於爲何不mock private的緣由,挺有意思的:數據庫

https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2數組

1.2 Mockito準備工做

###Maven###微信

經過Maven管理的,須要在項目的Pom.xml中增長以下的依賴:網絡

 <dependencies>

<dependency>

<groupId>org.mockito</groupId>

<artifactId>mockito-core</artifactId>

<version>2.7.19</version>

<scope>test</scope>

</dependency>

</dependencies>

 

 

在程序中能夠import org.mockito.Mockito,而後調用它的static方法。session

Maven用戶能夠聲明對mockito-core的依賴。 Mockito自動發佈到Bintray的中心,並同步到Maven Central Repository。maven

特別提醒:使用手工依賴關係管理的Legacy構建可使用1. *「mockito-all」分發。 它能夠從Mockito的Bintray存儲庫或Bintray的中心下載。 在可是Mockito 2. * 「mockito-all」發行已經中止,Mockito 2以上版本使用「mockito-core」。

官網下載中心:

http://search.maven.org/#search|gav|1|g%3A%22org.mockito%22%20AND%20a%3A%22mockito-core%22

目前最新版本爲2.7.19,因爲公司網絡網關問題,最好是去官網手工下載。

另外Mockito須要Junit配合使用,在Pom文件中一樣引入:

<dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
      </dependency>

而後爲了使代碼更簡潔,最好在測試類中導入靜態資源,還有爲了使用經常使用的junit關鍵字,也要引入junit的兩個類Before和Test:

import static org.mockito.Mockito.*;
import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test;

1.3 模擬對象

建立 Mock 對象的語法爲 mock(class or interface)。

Mock 對象的建立

mock(Class classToMock);
 mock(Class classToMock, String name)
 mock(Class classToMock, Answer defaultAnswer)
 mock(Class classToMock, MockSettings mockSettings)
 mock(Class classToMock, ReturnValues returnValues)

能夠對類和接口進行mock對象的建立,建立時能夠爲mock對象命名。對mock對象命名的好處是調試的時候容易辨認mock對象。

 

Mock對象的指望行爲和返回值設定

假設咱們建立了LinkedList類的mock對象:

 LinkedList mockedList = mock(LinkedList.class);

1.4 設置對象調用的預期返回值

經過 when(mock.someMethod()).thenReturn(value) 來設定 Mock 對象某個方法調用時的返回值。咱們能夠看看源碼中關於thenReturn方法的註釋:

 

使用when(mock.someMethod()).thenThrow(new RuntimeException) 的方式來設定當調用某個方法時拋出的異常。

 

以及Answer:

 

 

Answer 是個泛型接口。到調用發生時將執行這個回調,經過  Object[] args = invocation.getArguments();能夠拿到調用時傳入的參數,經過 Object mock = invocation.getMock();能夠拿到mock對象。

有些方法可能接口的參數爲一個Listener參數,若是咱們使用Answer打樁,咱們就能夠獲取這個Listener,而且在Answer函數中執行對應的回調函數,這對咱們瞭解函數的內部執行過成有很大的幫助。

使用doThrow(new RuntimeException(「clear exception」)).when(mockedList).clear();mockedList.clear();的方式Mock沒有返回值類型的函數:

doThrow(new RuntimeException()).when(mockedList).clear();

//將會 拋出 RuntimeException:

mockedList.clear();

這個實例表示當執行到mockedList.clear()時,將會拋出RuntimeException。其餘的doXXX執行與它相似。

例如 : doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod() 系列方法。

 

Spy函數:

你能夠爲真實對象建立一個監控(spy)對象,當你使用這個spy對象時,真實的對象也會被調用,除非它的函數被打樁。你應該儘可能少的使用spy對象,使用時也須要當心,例如spy對象能夠用來處理遺留代碼,Spy示例以下:

List list = new LinkedList();

//監控一個真實對象
 List spy = spy(list); //你能夠爲某些函數打樁  when(spy.size()).thenReturn(100); //使用這個將調用真實對象的函數  spy.add("one"); spy.add("two"); //打印"one"  System.out.println(spy.get(0)); //size() 將打印100  System.out.println(spy.size()); //交互驗證  verify(spy).add("one"); verify(spy).add("two");

理解監控真實對象很是重要,有時,在監控對象上使用when(Object)來進行打樁是不可能或者不切實際的。由於,當使用監控對象時,請考慮用doReturn、Answer、Throw()函數組來進行打樁,例如:

List list = new LinkedList();

List spy = spy(list);

//這是不可能的: 由於調用spy.get(0)時會調用真實對象的get(0)函數,此時會發生

//IndexOutOfBoundsException異常,由於真實對象是空的 when(spy.get(0)).thenReturn("foo");

//你須要使用 doReturn() 來打樁

doReturn("foo").when(spy).get(0);

Mockito並不會爲真實的對象代理函數調用,實際上它會複製真實對象,所以,若是你保留了真實對象而且與之交互,不要指望監控對象獲得正確的結果。當你在監控對象上調用一個沒有stub函數時,並不會調用真實對象的對應函數,你不會在真實對象上看到任何效果。

1.5 驗證被測試類方法

Mock 對象一旦創建便會自動記錄本身的交互行爲,因此咱們能夠有選擇的對它的 交互行爲進行驗證。在 Mockito 中驗證 Mock 對象交互行爲的方法是 verify(mock).someMethod(…)。最後 Assert() 驗證返回值是否和預期同樣。

1.6 Demo

 從網上找來一個最簡單的代碼實例,下面以具體代碼演示如何使用Mockito,代碼有三個類,分別以下:

Person類:

public class Person {
    private final int id; private final String name; public Person(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public String getName() { return name; } }

PersonDao類:

public interface PersonDao {
    Person getPerson(int id); boolean update(Person person); }

PersonService類:

 

public class PersonService {
    private final PersonDao personDao; public PersonService(PersonDao personDao) { this.personDao = personDao; } public boolean update(int id, String name) { Person person = personDao.getPerson(id); if (person == null) { return false; } Person personUpdate = new Person(person.getId(), name); return personDao.update(personUpdate); } }

 

仍然使用Junit自動生成測試類或者手工新建測試類:

 

 

測試代碼生成後,將默認assertfail的刪掉,輸入如下兩個測試方法:

import org.junit.Before;
import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.*; public class PersonServiceTest { private PersonDao mockDao; private PersonService personService; @Before public void setUp() throws Exception { //模擬PersonDao對象 mockDao = mock(PersonDao.class); when(mockDao.getPerson(1)).thenReturn(new Person(1, "Person1")); when(mockDao.update(isA(Person.class))).thenReturn(true); personService = new PersonService(mockDao); } @Test public void testUpdate() throws Exception { boolean result = personService.update(1, "new name"); assertTrue("must true", result); //驗證是否執行過一次getPerson(1) verify(mockDao, times(1)).getPerson(eq(1)); //驗證是否執行過一次update verify(mockDao, times(1)).update(isA(Person.class)); } @Test public void testUpdateNotFind() throws Exception { boolean result = personService.update(2, "new name"); assertFalse("must true", result); //驗證是否執行過一次getPerson(1) verify(mockDao, times(1)).getPerson(eq(1)); //驗證是否執行過一次update verify(mockDao, never()).update(isA(Person.class)); } }

注意:咱們對PersonDAO進行mock,而且設置stubbing,stubbing設置以下:

  • 當getPerson方法傳入1的時候,返回一個Person對象,不然默認返回空
  • 當調update方法的時候,返回true

這裏使用了兩個參數匹配器:

  isA():Object argument that implements the given class.

  eq():int argument that is equal to the given value

注:Mockito使用verify去校驗方法是否被調用,而後使用isA和eq這些內置的參數匹配器能夠更加靈活,

關於參數匹配器的詳細使用請參考官網文檔:https://static.javadoc.io/org.mockito/mockito-core/2.25.0/org/mockito/ArgumentMatchers.html

因爲官網的代碼和解釋很是詳細,此處就再也不贅述。

 

仍然調用Junit執行單元測試代碼,結果如圖所示:

驗證了兩種狀況:

  • 更新id爲1的Person的名字,預期:能在DAO中找到Person並更新成功
  • 更新id爲2的Person的名字,預期:不能在DAO中找到Person,更新失敗

這裏也能夠查看Eclipse拋出的異常信息:

Argument(s) are different! Wanted:

personDao.getPerson(1);

-> at PersonServiceTest.testUpdateNotFind(PersonServiceTest.java:41)

Actual invocation has different arguments:

personDao.getPerson(2);

-> at PersonService.update(PersonService.java:8)

2 單元測試與覆蓋率

一、Junit 二、JaCoCo 三、EclEmma

2 覆蓋率

覆蓋率以下圖顯示:

覆蓋率仍然使用JaCoCo和EclEmma:

l  未覆蓋代碼標記爲紅色

l  已覆蓋代碼會標記爲綠色

l  部分覆蓋的代碼標記爲黃色

顏色也能夠在Eclipse中自定義設置:

在Eclipse下方的狀態欄窗口,有一欄「Coverage」,點擊能夠顯示詳細的代碼覆蓋率:

如何導出爲Html格式的Report:

在Eclipse下方的Coverage欄鼠標右鍵選擇「Export Session…」,在彈出窗口中選擇export的目標爲「Coverage Report」以下圖:

點擊「Next」按鈕後,在接下來的彈出窗口選擇須要導出的session,Format

類型選擇「HTML report」,導出位置暫時選擇爲桌面,都選擇以後點擊「Finish」按鈕就生成好了。

在桌面上找到一個叫作index.html的頁面就是剛剛生成好的Coverage Report:

點擊文件夾能夠進入目錄,進一步查看子文件的覆蓋率:

 

 

附錄:參考文檔一覽

 

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

5分鐘瞭解Mockito:http://liuzhijun.iteye.com/blog/1512780

Mockito簡單介紹及示例:http://blog.csdn.net/huoshuxiao/article/details/6107835

Mockito淺談:http://www.jianshu.com/p/77db26b4fb54

單元測試利器-Mockito 中文文檔:http://blog.csdn.net/bboyfeiyu/article/details/52127551

Mockito使用指南 :http://blog.csdn.net/shensky711/article/details/52771493

JUnit+Mockito 單元測試(二):http://blog.csdn.net/zhangxin09/article/details/42422643

 

感謝閱讀!做者原創技術文章,轉載請註明出處

 

其餘推薦相關閱讀:

 

單元測試系列之一:如何使用JUnit、JaCoCo和EclEmma提升單元測試覆蓋率

 

測試系列之二:Mock工具Jmockit實戰

 

單元測試系列之三:JUnit單元測試規範

 

單元測試系列之四:Sonar平臺中項目主要指標以及代碼壞味道詳解

 

單元測試系列之五:Mock工具之Mockito實戰

 

單元測試系列之六:JUnit5 技術前瞻

 

單元測試系列之七:Sonar 數據庫表關係整理一(rule相關)

 

單元測試系列之八:Sonar 數據庫表關係整理一(續)

 

單元測試系列之九:Sonar 經常使用代碼規則整理(一)

 

單元測試系列之十:Sonar 經常使用代碼規則整理(二)

 

單元測試系列之十一:Jmockit之mock特性詳解

相關文章
相關標籤/搜索