一.前言
??關(guān)于這篇文章的起源,是第三次思沃大講堂的作業(yè)的題目中,有這樣一段話
把前兩問(wèn)做的類(lèi)集成起來(lái),寫(xiě)一個(gè)集成的單元測(cè)試,寫(xiě)一個(gè)集成測(cè)試。
問(wèn)題來(lái)了,集成的單元測(cè)試和集成測(cè)試有什么區(qū)別呢?
??集成測(cè)試(Integration Testing):是在單元測(cè)試的基礎(chǔ)上,將所有模塊按照概要設(shè)計(jì)要求組裝成為一個(gè)子系統(tǒng)或者系統(tǒng),進(jìn)行集成測(cè)試。一些模塊雖然能夠單獨(dú)工作,但并不能保證連接起來(lái)也能正常的工作,程序在某些局部反映不出來(lái)的問(wèn)題,在全局上很可能暴漏出來(lái),因此集成測(cè)試十分必要。
??集成的單元測(cè)試:按字面意思的理解,就是對(duì)該集成類(lèi)進(jìn)行單元測(cè)試。單元測(cè)試就是對(duì)已經(jīng)實(shí)現(xiàn)的軟件最小單元進(jìn)行測(cè)試以保證構(gòu)成軟件系統(tǒng)的各個(gè)單元的質(zhì)量。這么說(shuō)來(lái),在此處集成的單元測(cè)試和集成測(cè)試并無(wú)區(qū)別嗎?
??No~ 區(qū)別還是有的,集成的單元測(cè)試,首先是個(gè)單元測(cè)試,然后是對(duì)集成類(lèi)進(jìn)行單元測(cè)試,也就是說(shuō)只測(cè)試該類(lèi)的邏輯,而不用關(guān)注他所依賴(lài)的類(lèi)是否正確實(shí)現(xiàn)。如何不關(guān)注依賴(lài)類(lèi)的是否正確實(shí)現(xiàn)呢?這就是接下來(lái)我要介紹的Mock啦,這得感謝我的Buddy,讓我對(duì)Mock有了直白的認(rèn)識(shí),然后才關(guān)注Mock,從而進(jìn)一步了解。
二.為什么需要mock
??我們?cè)谧鰷y(cè)試的時(shí)候,往往會(huì)發(fā)現(xiàn)我們要測(cè)試的類(lèi)或方法會(huì)引用很多外部依賴(lài)的對(duì)象,而我們沒(méi)法控制這些外部依賴(lài)的對(duì)象,為了解決這個(gè)問(wèn)題,我們需要用到Mock來(lái)模擬這些外部依賴(lài)的對(duì)象,從而控制它們。舉個(gè)例子,service調(diào)用dao,即service依賴(lài)dao,我們可以用mock來(lái)模擬真實(shí)的dao調(diào)用,從而達(dá)到測(cè)試service的目的。
??模擬對(duì)象(Mock Object)可以取代真實(shí)對(duì)象的位置,用于測(cè)試一些與真實(shí)對(duì)象進(jìn)行交互或依賴(lài)于真實(shí)對(duì)象的功能,模擬對(duì)象背后的目的就是創(chuàng)建一個(gè)輕量級(jí)的,可以控制的對(duì)象來(lái)代替測(cè)試中需要的真實(shí)對(duì)象,模擬真實(shí)對(duì)象的行為和功能。
mock對(duì)象使用范疇
1.真實(shí)對(duì)象具有不可確定的行為,產(chǎn)生不可預(yù)測(cè)的效果。
2.真實(shí)對(duì)象很難被創(chuàng)建的。
3.真實(shí)對(duì)象的某些行為很難被觸發(fā)。
4.真實(shí)對(duì)象實(shí)際上還不存在的。
三.常見(jiàn)的mock框架
- jmock:通過(guò)mock對(duì)象來(lái)模擬一個(gè)對(duì)象的行為,從而隔離開(kāi)我們不關(guān)心的其他對(duì)象,使得測(cè)試變得簡(jiǎn)單。缺點(diǎn):在執(zhí)行前記錄期望行為,顯得很繁瑣。
- Mockito:Mockito通過(guò)在執(zhí)行后校驗(yàn)?zāi)男┖瘮?shù)已經(jīng)被調(diào)用,消除了對(duì)期望行為的需要,API非常簡(jiǎn)潔。缺點(diǎn):對(duì)于靜態(tài)函數(shù)、構(gòu)造函數(shù)、私有函數(shù)等還是無(wú)能為力。
- powermock:PowerMock是在Mockito的基礎(chǔ)上做出的擴(kuò)展。通過(guò)提供定制的類(lèi)加載器以及一些字節(jié)碼篡改技巧的應(yīng)用,PowerMock 實(shí)現(xiàn)了對(duì)靜態(tài)方法、構(gòu)造方法、私有方法以及 Final 方法的模擬支持,對(duì)靜態(tài)初始化過(guò)程的移除等強(qiáng)大的功能。缺點(diǎn):會(huì)對(duì)字節(jié)碼篡改,即測(cè)試時(shí)的字節(jié)碼與平時(shí)編譯出來(lái)的字節(jié)碼是不一樣的,而很多統(tǒng)計(jì)單元測(cè)試覆蓋率的插件是以字節(jié)碼來(lái)統(tǒng)計(jì)的,所以PowerMock編寫(xiě)的測(cè)試程序不能被統(tǒng)計(jì)進(jìn)覆蓋率。
推薦Mockito和powermock
四.Mockito簡(jiǎn)單介紹
一般使用Mockito需要執(zhí)行以下步驟:
1.模擬并替換測(cè)試代碼中外部依賴(lài)。
2.執(zhí)行測(cè)試代碼。
3.驗(yàn)證測(cè)試代碼是否被正確的執(zhí)行

使用注解(@Mock、@InjectMocks等)的話,必須實(shí)例化mock對(duì)象,有兩種方式實(shí)例化mock對(duì)象:
- @RunWith(MockitoJUnitRunner.class)
- MockitoAnnotations.initMocks(this)
當(dāng)我們需要配置某個(gè)方法的返回值時(shí),Mockito提供了鏈?zhǔn)降腁PI供我們方便的調(diào)用:
- when(mockObject.someMethod()).thenReturn(...)
用來(lái)定義當(dāng)條件滿(mǎn)足時(shí)函數(shù)的返回值。 - when(mockObject.someMethod()).thenReturn(...).thenReturn(...)
用來(lái)定義多個(gè)返回值的情況。 - doReturn(...).when(mockObject.someMethod())
- when(mockObject.someMethod()).thenThrow(new Runtime
Exception())
執(zhí)行某方法時(shí)拋出異常。 - doThrow(new RuntimeException()).when(mockObject.someMethod())
- 對(duì)void方法進(jìn)行預(yù)期設(shè)定
1.doNothing().when(mock.someMethod())
2.doThrow(new RuntimeException()).when(mock.someMethod())
3.doNothing().doThrow(new RuntimeException()).when(mock.someMethod()) - Mockito會(huì)自動(dòng)記錄自己的交互行為,可以用verify(…).methodXxx(…)語(yǔ)法來(lái)驗(yàn)證Xxx()方法是否按照預(yù)期進(jìn)行了調(diào)用
1.驗(yàn)證調(diào)用次數(shù):verify(mockObject,times(n)).someMethod(argument);//n為被調(diào)用的次數(shù)
2.驗(yàn)證超時(shí):verify(mockObject, timeout(100)).someMethod();
3.既驗(yàn)證調(diào)用次數(shù),又驗(yàn)證是否超時(shí):verify(mockObject, timeout(100).times(1)).someMethod();
需注意:
- 對(duì)于final、static方法,Mockito 無(wú)法對(duì)其 when(…).thenReturn(…) 操作。
五.Mockito使用實(shí)例
以第三次思沃大講堂的作業(yè)為例。
問(wèn)題描述:游戲開(kāi)始后,系統(tǒng)會(huì)隨機(jī)給出一個(gè)四位,每位都不重復(fù)的數(shù)字作為答案。由用戶(hù)輸入自己猜測(cè)的 四個(gè)數(shù)字。 系統(tǒng)會(huì)將兩個(gè)數(shù)字進(jìn)行對(duì)比,并給形出xAxB的提示, 比如”2A1B”。 如果數(shù)字猜對(duì)而且位置也對(duì),就是一個(gè)A。 如果數(shù)字猜對(duì)但位置不對(duì),就是一個(gè)B。 例如:系統(tǒng)給出”1234”,用戶(hù)輸入”1234” 返回”4A0B” 系統(tǒng)給出”1234”,用戶(hù)輸入”4321” 返回”0A4B”。
CompareNumber類(lèi):實(shí)現(xiàn)比較。只有一個(gè)函數(shù),該函數(shù)接受兩個(gè)參數(shù),一個(gè)是答案,一個(gè)是用戶(hù)輸 入的四位數(shù)。返回值是xAxB的字符串 。
AnswerGenerator類(lèi):生成隨機(jī)的四位無(wú)重復(fù)位數(shù)字。只有一個(gè)函數(shù),返回一個(gè)四位,每位都不重復(fù)隨機(jī)數(shù)。
Guess類(lèi):只有一個(gè)函數(shù),只有一個(gè)參數(shù)。把前兩問(wèn)做的類(lèi)集成起來(lái)。
GuessUnitTest類(lèi):對(duì)Guess類(lèi)寫(xiě)單元測(cè)試(即文首所說(shuō)的集成的單元測(cè)試)。
public class Guess {
private CompareNumber compareNumber = new CompareNumber();
private AnswerGenerator answerGenerator = new AnswerGenerator();
private int answer=answerGenerator.generatorFourDigits();
public String guessTheDigit(int guessDigit){
String result = compareNumber.compareToAnswer(answer,guessDigit);
return result;
}
}
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.lang.reflect.Field;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class GuessUnitTest {
@InjectMocks
private Guess guess = new Guess();
@Mock
private CompareNumber mockCompareNumber = new CompareNumber();
@Mock
private AnswerGenerator mockAnswerGenerator = new AnswerGenerator();
@Before
public void init() throws NoSuchFieldException, IllegalAccessException {
/*返回guess已聲明字段answer*/
Field f = guess.getClass().getDeclaredField("answer");
/*值為 true 則指示反射的對(duì)象在使用時(shí)應(yīng)該取消 Java 語(yǔ)言訪問(wèn)檢查*/
f.setAccessible(true);
/*將指f對(duì)象表示的字段answer設(shè)置為指定的值,即1234。*/
f.set(guess, 1234);
}
@Test
public void test_4A0B() {
when(mockCompareNumber.compareToAnswer(1234, 1234)).thenReturn("4A0B");
String result = guess.guessTheDigit(1234);
assertTrue("4A0B".equals(result));
}
@Test
public void test_0A4B() {
when(mockCompareNumber.compareToAnswer(1234, 4321)).thenReturn("0A4B");
String result = guess.guessTheDigit(4321);
assertTrue("0A4B".equals(result));
}
@Test
public void test_2A2B() {
when(mockCompareNumber.compareToAnswer(1234, 1432)).thenReturn("2A2B");
String result = guess.guessTheDigit(1432);
assertTrue("2A2B".equals(result));
}
}
說(shuō)明
@Mock:創(chuàng)建一個(gè)mock對(duì)象(模擬對(duì)象)。
@InjectMock:創(chuàng)建一個(gè)實(shí)例,@Mock注解創(chuàng)建的模擬對(duì)象將被注入到該實(shí)例中。
@Before:Junit注解,在每個(gè)測(cè)試執(zhí)行之前必須執(zhí)行的代碼。
@Test:Junit注解,標(biāo)明是一個(gè)測(cè)試方法。
Junit4常用注解
- @Before:初始化方法,在任何一個(gè)測(cè)試執(zhí)行之前必須執(zhí)行的代碼。
- @After:釋放資源,在任何測(cè)試執(zhí)行之后需要進(jìn)行的收尾工作。
- @Test:測(cè)試方法,表明這是一個(gè)測(cè)試方法。在Junit中將會(huì)自動(dòng)被執(zhí)行。
- @Ignore:忽略的測(cè)試方法,含義為“某些方法尚未完成,暫不參與此次測(cè)試”。測(cè)試結(jié)果提示你有幾個(gè)測(cè)試被忽略,而不是失敗。
- @BeforeClass:針對(duì)所有測(cè)試,在所有測(cè)試方法執(zhí)行前執(zhí)行一次。
- @AfterClass:針對(duì)所有測(cè)試,在所有測(cè)試方法執(zhí)行結(jié)束后執(zhí)行一次。
在Junit4中,單元測(cè)試用例的執(zhí)行順序:

每個(gè)測(cè)試方法的執(zhí)行順序:

六.Mockito學(xué)習(xí)資料
Mockito官網(wǎng):http://site.mockito.org/
Mockito官方文檔:https://static.javadoc.io/org.mockito/mockito-core/2.13.0/org/mockito/Mockito.html
Mockito中文文檔:http://blog.csdn.net/bboyfeiyu/article/details/52127551
Mockito Github:https://github.com/mockito/mockito