一.前言
??關(guān)于這篇文章的起源,是第三次思沃大講堂的作業(yè)的題目中,有這樣一段話
把前兩問做的類集成起來,寫一個集成的單元測試,寫一個集成測試。
問題來了,集成的單元測試和集成測試有什么區(qū)別呢?
??集成測試(Integration Testing):是在單元測試的基礎(chǔ)上,將所有模塊按照概要設(shè)計要求組裝成為一個子系統(tǒng)或者系統(tǒng),進(jìn)行集成測試。一些模塊雖然能夠單獨工作,但并不能保證連接起來也能正常的工作,程序在某些局部反映不出來的問題,在全局上很可能暴漏出來,因此集成測試十分必要。
??集成的單元測試:按字面意思的理解,就是對該集成類進(jìn)行單元測試。單元測試就是對已經(jīng)實現(xiàn)的軟件最小單元進(jìn)行測試以保證構(gòu)成軟件系統(tǒng)的各個單元的質(zhì)量。這么說來,在此處集成的單元測試和集成測試并無區(qū)別嗎?
??No~ 區(qū)別還是有的,集成的單元測試,首先是個單元測試,然后是對集成類進(jìn)行單元測試,也就是說只測試該類的邏輯,而不用關(guān)注他所依賴的類是否正確實現(xiàn)。如何不關(guān)注依賴類的是否正確實現(xiàn)呢?這就是接下來我要介紹的Mock啦,這得感謝我的Buddy,讓我對Mock有了直白的認(rèn)識,然后才關(guān)注Mock,從而進(jìn)一步了解。
二.為什么需要mock
??我們在做測試的時候,往往會發(fā)現(xiàn)我們要測試的類或方法會引用很多外部依賴的對象,而我們沒法控制這些外部依賴的對象,為了解決這個問題,我們需要用到Mock來模擬這些外部依賴的對象,從而控制它們。舉個例子,service調(diào)用dao,即service依賴dao,我們可以用mock來模擬真實的dao調(diào)用,從而達(dá)到測試service的目的。
??模擬對象(Mock Object)可以取代真實對象的位置,用于測試一些與真實對象進(jìn)行交互或依賴于真實對象的功能,模擬對象背后的目的就是創(chuàng)建一個輕量級的,可以控制的對象來代替測試中需要的真實對象,模擬真實對象的行為和功能。
mock對象使用范疇
1.真實對象具有不可確定的行為,產(chǎn)生不可預(yù)測的效果。
2.真實對象很難被創(chuàng)建的。
3.真實對象的某些行為很難被觸發(fā)。
4.真實對象實際上還不存在的。
三.常見的mock框架
- jmock:通過mock對象來模擬一個對象的行為,從而隔離開我們不關(guān)心的其他對象,使得測試變得簡單。缺點:在執(zhí)行前記錄期望行為,顯得很繁瑣。
- Mockito:Mockito通過在執(zhí)行后校驗?zāi)男┖瘮?shù)已經(jīng)被調(diào)用,消除了對期望行為的需要,API非常簡潔。缺點:對于靜態(tài)函數(shù)、構(gòu)造函數(shù)、私有函數(shù)等還是無能為力。
- powermock:PowerMock是在Mockito的基礎(chǔ)上做出的擴(kuò)展。通過提供定制的類加載器以及一些字節(jié)碼篡改技巧的應(yīng)用,PowerMock 實現(xiàn)了對靜態(tài)方法、構(gòu)造方法、私有方法以及 Final 方法的模擬支持,對靜態(tài)初始化過程的移除等強大的功能。缺點:會對字節(jié)碼篡改,即測試時的字節(jié)碼與平時編譯出來的字節(jié)碼是不一樣的,而很多統(tǒng)計單元測試覆蓋率的插件是以字節(jié)碼來統(tǒng)計的,所以PowerMock編寫的測試程序不能被統(tǒng)計進(jìn)覆蓋率。
推薦Mockito和powermock
四.Mockito簡單介紹
一般使用Mockito需要執(zhí)行以下步驟:
1.模擬并替換測試代碼中外部依賴。
2.執(zhí)行測試代碼。
3.驗證測試代碼是否被正確的執(zhí)行
使用注解(@Mock、@InjectMocks等)的話,必須實例化mock對象,有兩種方式實例化mock對象:
- @RunWith(MockitoJUnitRunner.class)
- MockitoAnnotations.initMocks(this)
當(dāng)我們需要配置某個方法的返回值時,Mockito提供了鏈?zhǔn)降腁PI供我們方便的調(diào)用:
- when(mockObject.someMethod()).thenReturn(...)
用來定義當(dāng)條件滿足時函數(shù)的返回值。 - when(mockObject.someMethod()).thenReturn(...).thenReturn(...)
用來定義多個返回值的情況。 - doReturn(...).when(mockObject.someMethod())
- when(mockObject.someMethod()).thenThrow(new Runtime
Exception())
執(zhí)行某方法時拋出異常。 - doThrow(new RuntimeException()).when(mockObject.someMethod())
- 對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會自動記錄自己的交互行為,可以用verify(…).methodXxx(…)語法來驗證Xxx()方法是否按照預(yù)期進(jìn)行了調(diào)用
1.驗證調(diào)用次數(shù):verify(mockObject,times(n)).someMethod(argument);//n為被調(diào)用的次數(shù)
2.驗證超時:verify(mockObject, timeout(100)).someMethod();
3.既驗證調(diào)用次數(shù),又驗證是否超時:verify(mockObject, timeout(100).times(1)).someMethod();
需注意:
- 對于final、static方法,Mockito 無法對其 when(…).thenReturn(…) 操作。
五.Mockito使用實例
以第三次思沃大講堂的作業(yè)為例。
問題描述:游戲開始后,系統(tǒng)會隨機(jī)給出一個四位,每位都不重復(fù)的數(shù)字作為答案。由用戶輸入自己猜測的 四個數(shù)字。 系統(tǒng)會將兩個數(shù)字進(jìn)行對比,并給形出xAxB的提示, 比如”2A1B”。 如果數(shù)字猜對而且位置也對,就是一個A。 如果數(shù)字猜對但位置不對,就是一個B。 例如:系統(tǒng)給出”1234”,用戶輸入”1234” 返回”4A0B” 系統(tǒng)給出”1234”,用戶輸入”4321” 返回”0A4B”。
CompareNumber類:實現(xiàn)比較。只有一個函數(shù),該函數(shù)接受兩個參數(shù),一個是答案,一個是用戶輸 入的四位數(shù)。返回值是xAxB的字符串 。
AnswerGenerator類:生成隨機(jī)的四位無重復(fù)位數(shù)字。只有一個函數(shù),返回一個四位,每位都不重復(fù)隨機(jī)數(shù)。
Guess類:只有一個函數(shù),只有一個參數(shù)。把前兩問做的類集成起來。
GuessUnitTest類:對Guess類寫單元測試(即文首所說的集成的單元測試)。
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 則指示反射的對象在使用時應(yīng)該取消 Java 語言訪問檢查*/
f.setAccessible(true);
/*將指f對象表示的字段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));
}
}
說明
@Mock:創(chuàng)建一個mock對象(模擬對象)。
@InjectMock:創(chuàng)建一個實例,@Mock注解創(chuàng)建的模擬對象將被注入到該實例中。
@Before:Junit注解,在每個測試執(zhí)行之前必須執(zhí)行的代碼。
@Test:Junit注解,標(biāo)明是一個測試方法。
Junit4常用注解
- @Before:初始化方法,在任何一個測試執(zhí)行之前必須執(zhí)行的代碼。
- @After:釋放資源,在任何測試執(zhí)行之后需要進(jìn)行的收尾工作。
- @Test:測試方法,表明這是一個測試方法。在Junit中將會自動被執(zhí)行。
- @Ignore:忽略的測試方法,含義為“某些方法尚未完成,暫不參與此次測試”。測試結(jié)果提示你有幾個測試被忽略,而不是失敗。
- @BeforeClass:針對所有測試,在所有測試方法執(zhí)行前執(zhí)行一次。
- @AfterClass:針對所有測試,在所有測試方法執(zhí)行結(jié)束后執(zhí)行一次。
在Junit4中,單元測試用例的執(zhí)行順序:
每個測試方法的執(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