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