Mock工具之Mockito实战
在實際項目中寫單元測試的過程中我們會發(fā)現(xiàn)需要測試的類有很多依賴,這些依賴項又會有依賴,導致在單元測試代碼里幾乎無法完成構建,尤其是當依賴項尚未構建完成時會導致單元測試無法進行。為了解決這類問題我們引入了Mock的概念,簡單的說就是模擬這些需要構建的類或者資源,提供給需要測試的對象使用。業(yè)內(nèi)的Mock工具有很多,也已經(jīng)很成熟了,這里我們將直接使用最流行的Mockito進行實戰(zhàn)演練,完成mockito教程。
Mock工具概述
1.1? Mockito簡介
?
EasyMock 以及 Mockito 都因為可以極大地簡化單元測試的書寫過程而被許多人應用在自己的工作中,但是這兩種 Mock 工具都不可以實現(xiàn)對靜態(tài)函數(shù)、構造函數(shù)、私有函數(shù)、Final 函數(shù)以及系統(tǒng)函數(shù)的模擬,但是這些方法往往是我們在大型系統(tǒng)中需要的功能。
另外,關于更多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,然后調(diào)用它的static方法。
Maven用戶可以聲明對mockito-core的依賴。 Mockito自動發(fā)布到Bintray的中心,并同步到Maven Central Repository。
特別提醒:使用手工依賴關系管理的Legacy構建可以使用1. *“mockito-all”分發(fā)。 它可以從Mockito的Bintray存儲庫或Bintray的中心下載。 在但是Mockito 2. * “mockito-all”發(fā)行已經(jīng)停止,Mockito 2以上版本使用“mockito-core”。
官網(wǎng)下載中心:
http://search.maven.org/#search|gav|1|g%3A%22org.mockito%22%20AND%20a%3A%22mockito-core%22
目前最新版本為2.7.19,由于公司網(wǎng)絡網(wǎng)關問題,最好是去官網(wǎng)手工下載。
另外Mockito需要Junit配合使用,在Pom文件中同樣引入:
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency>然后為了使代碼更簡潔,最好在測試類中導入靜態(tài)資源,還有為了使用常用的junit關鍵字,也要引入junit的兩個類Before和Test:
import static org.mockito.Mockito.*; import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test;1.3 模擬對象
創(chuàng)建 Mock 對象的語法為 mock(class or interface)。
Mock 對象的創(chuàng)建
mock(Class classToMock);mock(Class classToMock, String name)mock(Class classToMock, Answer defaultAnswer)mock(Class classToMock, MockSettings mockSettings)mock(Class classToMock, ReturnValues returnValues)可以對類和接口進行mock對象的創(chuàng)建,創(chuàng)建時可以為mock對象命名。對mock對象命名的好處是調(diào)試的時候容易辨認mock對象。
?
Mock對象的期望行為和返回值設定
假設我們創(chuàng)建了LinkedList類的mock對象:
?LinkedList mockedList = mock(LinkedList.class);
1.4 設置對象調(diào)用的預期返回值
通過 when(mock.someMethod()).thenReturn(value) 來設定 Mock 對象某個方法調(diào)用時的返回值。我們可以看看源碼中關于thenReturn方法的注釋:
?
使用when(mock.someMethod()).thenThrow(new RuntimeException) 的方式來設定當調(diào)用某個方法時拋出的異常。
?
以及Answer:
?
?
Answer 是個泛型接口。到調(diào)用發(fā)生時將執(zhí)行這個回調(diào),通過? Object[] args = invocation.getArguments();可以拿到調(diào)用時傳入的參數(shù),通過 Object mock = invocation.getMock();可以拿到mock對象。
有些方法可能接口的參數(shù)為一個Listener參數(shù),如果我們使用Answer打樁,我們就可以獲取這個Listener,并且在Answer函數(shù)中執(zhí)行對應的回調(diào)函數(shù),這對我們了解函數(shù)的內(nèi)部執(zhí)行過成有很大的幫助。
使用doThrow(new RuntimeException(“clear exception”)).when(mockedList).clear();mockedList.clear();的方式Mock沒有返回值類型的函數(shù):
doThrow(new RuntimeException()).when(mockedList).clear();
//將會 拋出 RuntimeException:
mockedList.clear();
這個實例表示當執(zhí)行到mockedList.clear()時,將會拋出RuntimeException。其他的doXXX執(zhí)行與它類似。
例如 : doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod() 系列方法。
?
Spy函數(shù):
你可以為真實對象創(chuàng)建一個監(jiān)控(spy)對象,當你使用這個spy對象時,真實的對象也會被調(diào)用,除非它的函數(shù)被打樁。你應該盡量少的使用spy對象,使用時也需要小心,例如spy對象可以用來處理遺留代碼,Spy示例如下:
List list = new LinkedList();//監(jiān)控一個真實對象List spy = spy(list);//你可以為某些函數(shù)打樁when(spy.size()).thenReturn(100);//使用這個將調(diào)用真實對象的函數(shù)spy.add("one");spy.add("two");//打印"one"System.out.println(spy.get(0));//size() 將打印100System.out.println(spy.size());//交互驗證verify(spy).add("one"); verify(spy).add("two");理解監(jiān)控真實對象非常重要,有時,在監(jiān)控對象上使用when(Object)來進行打樁是不可能或者不切實際的。因為,當使用監(jiān)控對象時,請考慮用doReturn、Answer、Throw()函數(shù)組來進行打樁,例如:
List list = new LinkedList();
List spy = spy(list);
//這是不可能的: 因為調(diào)用spy.get(0)時會調(diào)用真實對象的get(0)函數(shù),此時會發(fā)生
//IndexOutOfBoundsException異常,因為真實對象是空的 when(spy.get(0)).thenReturn("foo");
//你需要使用 doReturn() 來打樁
doReturn("foo").when(spy).get(0);
Mockito并不會為真實的對象代理函數(shù)調(diào)用,實際上它會復制真實對象,因此,如果你保留了真實對象并且與之交互,不要期望監(jiān)控對象得到正確的結果。當你在監(jiān)控對象上調(diào)用一個沒有stub函數(shù)時,并不會調(diào)用真實對象的對應函數(shù),你不會在真實對象上看到任何效果。
1.5 驗證被測試類方法
Mock 對象一旦建立便會自動記錄自己的交互行為,所以我們可以有選擇的對它的 交互行為進行驗證。在 Mockito 中驗證 Mock 對象交互行為的方法是 verify(mock).someMethod(…)。最后 Assert() 驗證返回值是否和預期一樣。
1.6 Demo
?從網(wǎng)上找來一個最簡單的代碼實例,下面以具體代碼演示如何使用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;@Beforepublic 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);}@Testpublic void testUpdate() throws Exception {boolean result = personService.update(1, "new name");assertTrue("must true", result);//驗證是否執(zhí)行過一次getPerson(1)verify(mockDao, times(1)).getPerson(eq(1));//驗證是否執(zhí)行過一次updateverify(mockDao, times(1)).update(isA(Person.class));}@Testpublic void testUpdateNotFind() throws Exception {boolean result = personService.update(2, "new name");assertFalse("must true", result);//驗證是否執(zhí)行過一次getPerson(1)verify(mockDao, times(1)).getPerson(eq(1));//驗證是否執(zhí)行過一次updateverify(mockDao, never()).update(isA(Person.class));} }注意:我們對PersonDAO進行mock,并且設置stubbing,stubbing設置如下:
- 當getPerson方法傳入1的時候,返回一個Person對象,否則默認返回空
- 當調(diào)update方法的時候,返回true
這里使用了兩個參數(shù)匹配器:
isA():Object argument that implements the given class.
eq():int argument that is equal to the given value
注:Mockito使用verify去校驗方法是否被調(diào)用,然后使用isA和eq這些內(nèi)置的參數(shù)匹配器可以更加靈活,
關于參數(shù)匹配器的詳細使用請參考官網(wǎng)文檔:https://static.javadoc.io/org.mockito/mockito-core/2.25.0/org/mockito/ArgumentMatchers.html
由于官網(wǎng)的代碼和解釋非常詳細,此處就不再贅述。
?
仍然調(diào)用Junit執(zhí)行單元測試代碼,結果如圖所示:
驗證了兩種情況:
- 更新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 單元測試與覆蓋率
1、Junit 2、JaCoCo 3、EclEmma
2 覆蓋率
覆蓋率如下圖顯示:
覆蓋率仍然使用JaCoCo和EclEmma:
l? 未覆蓋代碼標記為紅色
l? 已覆蓋代碼會標記為綠色
l? 部分覆蓋的代碼標記為黃色
顏色也可以在Eclipse中自定義設置:
在Eclipse下方的狀態(tài)欄窗口,有一欄“Coverage”,點擊可以顯示詳細的代碼覆蓋率:
如何導出為Html格式的Report:
在Eclipse下方的Coverage欄鼠標右鍵選擇“Export Session…”,在彈出窗口中選擇export的目標為“Coverage Report”如下圖:
點擊“Next”按鈕后,在接下來的彈出窗口選擇需要導出的session,Format
類型選擇“HTML report”,導出位置暫時選擇為桌面,都選擇之后點擊“Finish”按鈕就生成好了。
在桌面上找到一個叫做index.html的頁面就是剛剛生成好的Coverage Report:
點擊文件夾可以進入目錄,進一步查看子文件的覆蓋率:
?
?
附錄:參考文檔一覽
?Mockito官網(wǎng):?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
?
感謝閱讀!作者原創(chuàng)技術文章,轉(zhuǎn)載請注明出處
?看完點個贊唄,難道想白嫖不成?更多內(nèi)容請訪問微信公眾號 :三國測,掃碼關注喲!
其他推薦相關閱讀:
單元測試系列之一:如何使用JUnit、JaCoCo和EclEmma提高單元測試覆蓋率
單元測試系列之二:Mock工具Jmockit實戰(zhàn)
單元測試系列之三:JUnit單元測試規(guī)范
單元測試系列之四:Sonar平臺中項目主要指標以及代碼壞味道詳解
單元測試系列之五:Mock工具之Mockito實戰(zhàn)
單元測試系列之六:JUnit5 技術前瞻
單元測試系列之七:Sonar 數(shù)據(jù)庫表關系整理一(rule相關)
單元測試系列之八:Sonar 數(shù)據(jù)庫表關系整理一(續(xù))
單元測試系列之九:Sonar 常用代碼規(guī)則整理(一)
單元測試系列之十:Sonar 常用代碼規(guī)則整理(二)
單元測試系列之十一:Jmockit之mock特性詳解
總結
以上是生活随笔為你收集整理的Mock工具之Mockito实战的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JSR 303 – Bean Valid
- 下一篇: java并发之SynchronousQu