歡迎使用CSDN-markdown編輯器

每次使用各種形形色色的單元測試Mock框架都比較暈,因爲寫的不夠多,等學會了,又流行了一個新的框架,思考爲什麼老是記不住以及爲什麼每次都不能胸有成竹的說自己掌握了,想想估計是因爲每次都是現學現用,比較零散,不成體系,所以寫下這個記錄,彙總下到底應該學習,一方面可以幫助在新學一個單元測試mock框架的時候,按照這個順序學,學完之後,按照這個步驟寫CASE;另一方面在使用mockito/powermock時,直接根據場景複製代碼。

必知必會(一)- 搞出“假”對象

既然是單元測試的Mock使用, 第一步要學的是怎麼搞出一個假的對象,然後後續工作其實都是圍繞這個假對象做文章。

根據應用場景不同可以劃分爲2種方式:

(1)Mock: 製造一個完全的假對象;

根據策略的不同,可以定義不同默認行爲的假對象,例如:

所有方法不進行任何真實調用,都統一返回null;

Apple apple= Mockito.mock(Apple.class);

所有方法調用是真實調用

Apple apple= Mockito.mock(Apple.class,Mockito.CALLS_REAL_METHODS);

(2)Spy: 製造一個假對象,但是是基於已有一個真實的對象。

滿足的需求是,大多方法想用真實實例來調用,只想定製實例內部的一些方法。

Apple apple = new Apple();
Apple spiedApple= Mockito.spy(apple);

必知必會(二)- 綁定上“假”對象

第一步搞出假對象後,不會就自動使用上了,否則別人不需要用mock的測試怎麼測?所以第二步要做的是讓自己的Mock對象使用上,即綁定上被測目標和mocked對象,思考一個類如何使用另外一個類:

1 被測目標自己不創建,而是需要使用者傳遞進去的方式:

(a)作爲構造器參數直接傳遞進去;
(b)使用Set系方法傳遞進去;

2 被測目標負責創建

本質上創建都是new的過程(PS:除了靜態類), 所以mock掉new,讓new返回要mocked的對象,就搞定所有的事情,但是從被測目標看,不可能都是new,可能這個new離被測目標還是有一定的距離:例如使用工廠類,使用spring的@autowire的等等,所以從代碼層次看有以下幾種情況:

(1) Apple apple = new Apple();
(2) apple = AppleFactory.getIntance();
(3)
@Autowired
private Apple apple;

(a)針對直接new的方式:讓new出一個對象都返回mock的實例

class AppleTree{

private Apple apple= new Apple();

}

@RunWith(PowerMockRunner.class)
@PrepareForTest({AppleTree.class}) //don't miss this statement and pay more attention it is caller for Apple, not Apple.
public class TestNewObject { 

@Test
public void test() throws Exception {
Apple apple= Mockito.mock(Apple.class);
PowerMockito.whenNew(Apple.class).withNoArguments().thenReturn(apple);
}

(b)針對使用其他類來創建:mock創建方法

一般都是靜態工廠這種情況,如果不是“靜態工廠”,是另外一個實例的普通方法創建的,則需要mock那個實例了。
這裏僅考慮一般情況,即面對靜態工廠方法,mockito暫時不支持靜態類的mock,所以需要結合powermock:

@RunWith(PowerMockRunner.class)
@PrepareForTest({AppleFactory.class}) //don't miss this statement
public class TestStaticMethod {

@Test
public void test() throws Exception {
Apple apple= Mockito.mock(Apple.class);
PowerMockito.mockStatic(AppleFactory.class);
PowerMockito.when(AppleFactory.getInstance())
.thenReturn(apple);
}

(c)還有一種情況是使用框架自動創建的,例如使用Spring的@Autowired
此時可以使用JAVA反射來直接設置進去,但是既然是使用mock工具,也可以使用標準點的方式,例如:

Apple apple= Mockito.mock(Apple.class);
Whitebox.setInternalState(testAppleInstance, "apple", apple);

必知必會(三)- Mock對象上做文章-僞造行爲

學完前面2步後,就可以開始考慮幹活了,既然搞出假的mock對象,不可能不去做一些“假動作”: 匹配上一個方法,然後做出一個行爲:

其中匹配包括2種:

粗略匹配:

Mockito.when(mockedApple.getOwner(Mockito.anyString()).thenReturn("tom");

精確匹配:

Mockito.when(mockedApple.getOwner(Mockito.eq("somegstring"))).thenReturn("tom");

行爲包括以下三種:

(1) 定義方法非真實調用;

設置其返回值:

Mockito.when(mockedApple.getOwner()).thenReturn("tom");

設置其拋出異常:

Mockito.when(mockedApple.getOwner()).thenThrow(new RuntimeException());

(2)定義方法去進行真實調用:

Mockito.when(mockedApple.getNumbers()).thenCallRealMethod();

(3)自適應變化:

例如設置每次返回的不同可以使用:

 when(mockedApple.getOwner())
  .thenReturn("one")  //第一次行爲
  .thenCallRealMethod() //第二次行爲
  .thenThrow(new RuntimeException()); //第三次行爲

其他形式的各種高級搞法,不考慮。

必知必會(四)- Mock對象上做文章-驗證行爲

不考慮本身case就可以寫出驗證點,有時候需要驗證一些mocked對象上的行爲來驗證case是否成功,按照需要驗證的要點來看:
(1)驗證調用與否或調用次數

 Mockito.verify(mockedApple, Mockito.times(2)).someMethod(Mockito.anyString());
 Mockito.verify(mockedApple, Mockito.never()).someMethod(Mockito.anyString());

(2) 驗證調用時間

 Mockito.verify(mockedApple, Mockito.timeout(10)).someMethod(Mockito.anyString());

(3)驗證調用參數值

方式1:Matcher-直接驗證參數

簡單校驗:

 Mockito.verify(mockedApple, times(2)).someMethod(Mockito.eq("expectedString")); //mockito要求此處不能直接寫"expectedString"

自動義校驗方法:

使用Mockito.argThat+ArgumentMatcher(Matchers.argThat(Matcher matcher)
):

Mockito.verify(mockedApple).someMethod(Mockito.argThat(new ArgumentMatcher<String>(){
            @Override
            public boolean matches(String argument) {
                return argument.equals("expectedString");
}}));

方式2:Captor-捕獲出參數,然後校驗

使用ArgumentCaptor捕獲參數,然後進一步處理的

ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
Mockito.verify(mockedApple).someMethod(argument.capture());
String value = argument.getValue();
Assert.assertEqual(value, expectedString);

區別:
Also, sometimes ArgumentCaptor may be a better fit than custom matcher. For example, if custom argument matcher is not likely to be reused or you just need it to assert on argument values to complete verification of behavior.

(4) 驗證調用順序

主要包括兩種,一種是同一個mock對象的方法調用順序,另外一種是跨mock對象的方法調用順序驗證,分別參考一下兩種示例:

InOrder inOrder = Mockito.inOrder(mockedApple);
inOrder.verify(mockedApple).firstMethodCallName();
inOrder.verify(mockedApple).secondMethodCallName();
InOrder inOrder = Mockito.inOrder(mockedApple,mockedOrange);
inOrder.verify(mockedApple).methodCallName();
inOrder.verify(mockedOrange).methodCallName();

對於各種驗證,有時候需要reset mock對象,以便處理共享等問題,可以使用Mockito.reset()。

總結:

對於一個新的單元測試框架大體要搞清楚幾件事情:“僞造對象-綁定對象-定製對象動作-驗證” ,核心關鍵是mock/spy it then when customized match one method do something and verify after executed寫具體case的時候,也可以follow四個步驟來搞。另外上面演示的都是基本要點,其他都是各種形式的變種或高級用法,同時每種框架都有自己的特殊要求,必須遵從。

發佈了177 篇原創文章 · 獲贊 147 · 訪問量 166萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章