1. 程式人生 > >Mock 模擬測試簡介及 Mockito 使用入門

Mock 模擬測試簡介及 Mockito 使用入門

Mock 是什麼
mock 測試就是在測試過程中,對於某些不容易構造或者不容易獲取的物件,用一個虛擬的物件來建立以便測試的測試方法。這個虛擬的物件就是mock物件。mock物件就是真實物件在除錯期間的代替品。

簡單的看一張圖

 

我們在測試類 A 時,類 A 需要呼叫類 B 和類 C,而類 B 和類 C 又需要呼叫其他類如 D、E、F 等,假如類 D、E、F 構造很耗時又或者呼叫很耗時的話是非常不便於測試的(比如是 DAO 類,每次訪問資料庫都很耗時)。所以我們引入 Mock 物件。

 

如上圖,我們將類 B 和類 C 替換成 Mock 物件,在呼叫類 B 和類 C 的方法時,用 Mock 物件的方法來替換(當然我們要自己設定引數和期望結果)而不用實際去呼叫其他類。這樣測試效率會高很多。

一句話為什麼要 Mock:因為我們實際編寫程式都不會是一個簡單類,而是有著複雜依賴關係的類,Mock 物件讓我們在不依賴具體物件的情況下完成測試。

Mock 的關鍵點
Mock 物件
模擬物件的概念就是我們想要建立一個可以替代實際物件的物件,這個模擬物件要可以通過特定引數呼叫特定的方法,並且能返回預期結果。

Stub(樁)
樁指的是用來替換具體功能的程式段。樁程式可以用來模擬已有程式的行為或是對未完成開發程式的一種臨時替代。

比如我們有一個獲取溫度的函式。

public double getTemperature(String Position) {
double ret = TemperatureRead(Position);
}
1
2
3
但是 TemperatureRead 函式要呼叫具體的硬體裝置,而硬體裝置沒準備好。

我們可以用 Stub 來替換

public double TemperatureRead(String position) {
return 28;
}
1
2
3
Stub:For replacing a method with code that returns a specified result

Mock:A stub with an expectations that the method gets called

設定預期
通過設定預期明確 Mock 物件執行時會發生什麼,比如返回特定的值、丟擲一個異常、觸發一個事件等,又或者呼叫一定的次數。

驗證預期的結果
設定預期和驗證預期是同時進行的。設定預期在呼叫測試類的函式之前完成,驗證預期則在它之後。所以,首先你設定好預期結果,然後去驗證你的預期結果是否正確。

Mock 的好處是什麼
提前建立測試; TDD(測試驅動開發)
這是個最大的好處吧。如果你建立了一個Mock那麼你就可以在service介面建立之前寫Service Tests了,這樣你就能在開發過程中把測試新增到你的自動化測試環境中了。換句話說,模擬使你能夠使用測試驅動開發。

團隊可以並行工作
這類似於上面的那點;為不存在的程式碼建立測試。但前面講的是開發人員編寫測試程式,這裡說的是測試團隊來建立。當還沒有任何東西要測的時候測試團隊如何來建立測試呢?模擬並針對模擬測試!這意味著當service藉口需要測試時,實際上QA團隊已經有了一套完整的測試元件;沒有出現一個團隊等待另一個團隊完成的情況。這使得模擬的效益型尤為突出了。

你可以建立一個驗證或者演示程式
由於Mocks非常高效,Mocks可以用來建立一個概念證明,作為一個示意圖,或者作為一個你正考慮構建專案的演示程式。這為你決定專案接下來是否要進行提供了有力的基礎,但最重要的還是提供了實際的設計決策。

為無法訪問的資源編寫測試
這個好處不屬於實際效益的一種,而是作為一個必要時的“救生圈”。有沒有遇到這樣的情況?當你想要測試一個service介面,但service需要經過防火牆訪問,防火牆不能為你開啟或者你需要認證才能訪問。遇到這樣情況時,你可以在你能訪問的地方使用MockService替代,這就是一個“救生圈”功能。

Mock 可以交給使用者
在有些情況下,某種原因你需要允許一些外部來源訪問你的測試系統,像合作伙伴或者客戶。這些原因導致別人也可以訪問你的敏感資訊,而你或許只是想允許訪問部分測試環境。在這種情況下,如何向合作伙伴或者客戶提供一個測試系統來開發或者做測試呢?最簡單的就是提供一個mock,無論是來自於你的網路或者客戶的網路。soapUI mock非常容易配置,他可以執行在soapUI或者作為一個war包釋出到你的java伺服器裡面。

隔離系統
有時,你希望在沒有系統其他部分的影響下測試系統單獨的一部分。由於其他系統部分會給測試資料造成干擾,影響根據資料收集得到的測試結論。使用mock你可以移除掉除了需要測試部分的系統依賴的模擬。當隔離這些mocks後,mocks就變得非常簡單可靠,快速可預見。這為你提供了一個移除了隨機行為,有重複模式並且可以監控特殊系統的測試環境。

Mockito
介紹
Mockito 是一個簡單的流行的 Mock 框架。它能夠幫我們建立 Mock 物件,保持單元測試的獨立性。

使用它只需要在 Maven 中新增依賴即可。

<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>2.0.2-beta</version>
</dependency>
1
2
3
4
5
6
建立 Mock 物件
通過方法建立
class CreateMock {
@Before
public void setup() {
mockUserDao = mock(UserDao.class);
userService = new UserServiceImpl();
userService.setUserDao(mockUserDao);
}
}
1
2
3
4
5
6
7
8
通過註解建立
class CreateMock {
@Mock
UserDao mockUserDao;

@InjectMocks
private UserServiceImpl userService;

@Before
public void setUp() {
//初始化物件的註解
MockitoAnnotations.initMocks(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
一個簡單的例子:
package Mockito;

import org.junit.Assert;
import org.junit.Test;
import java.util.List;

import static org.mockito.Mockito.*;

public class MyTest {
@Test
public void myTest() {
/* 建立 Mock 物件 */
List list = mock(List.class);
/* 設定預期,當呼叫 get(0) 方法時返回 "111" */
when(list.get(0)).thenReturn("111");

Assert.assertEquals("asd", 1, 1);
/* 設定後返回期望的結果 */
System.out.println(list.get(0));
/* 沒有設定則返回 null */
System.out.println(list.get(1));

/* 對 Mock 物件設定無效 */
list.add("12");
list.add("123");
/* 返回之前設定的結果 */
System.out.println(list.get(0));
/* 返回 null */
System.out.println(list.get(1));
/* size 大小為 0 */
System.out.println(list.size());

/* 驗證操作,驗證 get(0) 呼叫了 2 次 */
verify(list, times(2)).get(0);

/* 驗證返回結果 */
String ret = (String)list.get(0);
Assert.assertEquals(ret, "111");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40


從上面程式碼能看出一般使用的步驟:

設定方法預期返回
通過 when(list.get(0)).thenReturn(“111”); 來設定當呼叫 list.get(0) 時返回 “111”,該方法就是 Stub,替換我們實際的操作。

驗證方法呼叫
該方法驗證 get(0) 方法呼叫的次數 verify(list, times(2)).get(0);,還可以設定是否呼叫過,呼叫時間等等。

驗證返回值
Assert.assertEquals(ret, “111”); 方法驗證 Mock 物件方法呼叫後的返回值是否達到預期。

Mockito 使用
設定 Mock 物件期望和返回值
/* 表示第一次呼叫 someMethod() 返回 value1 第二次呼叫返回 value2 */
when(mock.someMethod()).thenReturn(value1).thenReturn(value2);
when(mock.someMethod()).thenReturn(value1, value2);
/* 也可以設定兩次 */
when(mock.someMethod()).thenReturn(value1);
when(mock.someMethod()).thenReturn(value2);
1
2
3
4
5
6
另外一種寫法 doReturn()

/* 表示第一次呼叫 someMethod() 返回 value1 第二次呼叫返回 value2 */
doReturn(value1).doReturn(value2).when(mock).someMethod();
/* 若返回 void,則設定為 doNothing() */
doNothing().when(mock).someMethod();
1
2
3
4
對方法設定返回異常
/* 當呼叫 someMethod() 方法時會丟擲異常 */
when(mock.someMethod()).thenThrow(new RuntimeException());
/* 對 void 方法設定 */
doThrow(new RuntimeException()).when(mock).someMethod();
1
2
3
4
引數匹配器
我們不一定要固定 Stub 呼叫時的引數,如 get(0)。可以通過引數匹配器來呼叫。

when(list.get(anyInt())).thenReturn("hello");
1
Mock 物件的行為驗證
package Mockito;

import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.testng.annotations.Test;

import java.util.List;
import static org.mockito.Mockito.*;

/**
* Created by andy.wwh on 2016/7/18.
*/
public class Behavior {
@Test
public void behaviorCheck() {
List mock1 = mock(List.class);
List mock2 = mock(List.class);

/* 設定預期 */
when(mock1.get(0)).thenReturn("hello world");
when(mock1.get(1)).thenReturn("hello world");
when(mock2.get(0)).thenReturn("hello world");
mock1.get(0);

/* 驗證方法呼叫一次 */
verify(mock1).get(0);
mock1.get(0);
/* 驗證方法呼叫兩次 */
verify(mock1, times(2)).get(0);
/* 驗證方法從未被呼叫過 */
verify(mock2, never()).get(0);
/* 驗證方法 100 毫秒內呼叫兩次 */
verify(mock1, timeout(100).times(2)).get(anyInt());

/* 設定方法呼叫順序 */
InOrder inOrder = inOrder(mock1, mock2);
inOrder.verify(mock1, times(2)).get(0);
inOrder.verify(mock2, never()).get(1);

/* 查詢是否存在被呼叫,但未被 verify 驗證的方法 */
verifyNoMoreInteractions(mock1, mock2);
/* 驗證 Mock 物件是否沒有交發生 */
verifyZeroInteractions(mock1, mock2);

/* 引數捕獲器 */
ArgumentCaptor<Integer> argumentCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mock1, times(2)).get(argumentCaptor.capture());
System.out.println("argument:" + argumentCaptor.getValue());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
驗證呼叫次數
verify(mock1, timeout(100).times(2)).get(anyInt());

除了程式碼中的方法,Mockito 還提供了
- never() 沒有被呼叫,相當於times(0)
- atLeast(N) 至少被呼叫N次
- atLeastOnce() 相當於atLeast(1)
- atMost(N) 最多被呼叫N次

超時驗證
通過 timeout 我們可以進行驗證程式執行時間是否符合規則。

方法呼叫順序
Inorder 可以驗證方法呼叫的順序

verifyNoMoreInteractions 和 verifyZeroInteractions
verifyNoMoreInteractions:查詢是否存在被呼叫,但未被 verify 驗證的方法

verifyZeroInteractions:verifyZeroInteractions

ArgumentCaptor 引數捕獲器
可在驗證時對方法的引數進行捕獲,最後驗證捕獲的引數值。如果方法有多個引數都要捕獲驗證,那就需要建立多個ArgumentCaptor物件處理。

Spy 物件驗證
Mock 操作的全是虛擬物件。即使我們設定了 when(list.get(0)).thenReturn(1),我們呼叫如 size() 方法返回的還是 0。Mockito 還給我們提供了一種對真實物件操作的方法——Spy

做一個簡單的比較:

package Mockito;

import org.testng.annotations.Test;

import java.util.List;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
* Created by andy.wwh on 2016/7/18.
*/
public class MockObject {
@Test
public void MockTest() {
List list = mock(List.class);

when(list.get(0)).thenReturn("hello world");

System.out.println(list.get(0));

System.out.println(list.size());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24


package Mockito;

import org.testng.annotations.Test;

import java.util.LinkedList;
import java.util.List;

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

/**
* Created by andy.wwh on 2016/7/18.
*/
public class MockObject {
@Test
public void MockTest() {
/* 建立真實物件 */
List list = new LinkedList();
List spy = spy(list);

spy.add("hello");

when(spy.get(0)).thenReturn("hello world");

System.out.println(spy.get(0));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27


看個官網的例子:

@Test
public void spyTest() {
List list = new LinkedList();
List spy = spy(list);
// optionally, you can stub out some methods:
when(spy.size()).thenReturn(100);
// using the spy calls real methods
spy.add("one");
spy.add("two");
// prints "one" - the first element of a list
System.out.println(spy.get(0));
// size() method was stubbed - 100 is printed
System.out.println(spy.size());
// optionally, you can verify
verify(spy).add("one");
verify(spy).add("two");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
參考:

Mockito 初探

Mockito:一個強大的用於 Java 開發的模擬測試框架
---------------------
作者:I_myours
來源:CSDN
原文:https://blog.csdn.net/wwh578867817/article/details/51934404
版權宣告:本文為博主原創文章,轉載請附上博文連結!