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); }
但是 TemperatureRead 函數要調用具體的硬件設備,而硬件設備沒準備好。
我們可以用 Stub 來替換
public double TemperatureRead(String position) { return 28; }
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>
創建 Mock 對象
通過方法創建
class CreateMock { @Before public void setup() { mockUserDao = mock(UserDao.class); userService = new UserServiceImpl(); userService.setUserDao(mockUserDao); } }
通過註解創建
class CreateMock { @Mock UserDao mockUserDao; @InjectMocks private UserServiceImpl userService; @Before public void setUp() { //初始化對象的註解 MockitoAnnotations.initMocks(this); } }
一個簡單的例子:
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"); } }
從上面代碼能看出一般使用的步驟:
設置方法預期返回
通過 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);
另外一種寫法 doReturn()
/* 表示第一次調用 someMethod() 返回 value1 第二次調用返回 value2 */ doReturn(value1).doReturn(value2).when(mock).someMethod(); /* 若返回 void,則設置為 doNothing() */ doNothing().when(mock).someMethod();
對方法設定返回異常
/* 當調用 someMethod() 方法時會拋出異常 */ when(mock.someMethod()).thenThrow(new RuntimeException()); /* 對 void 方法設定 */ doThrow(new RuntimeException()).when(mock).someMethod();
參數匹配器
我們不一定要固定 Stub 調用時的參數,如 get(0)。可以通過參數匹配器來調用。
when(list.get(anyInt())).thenReturn("hello");
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()); } }
驗證調用次數
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()); } }
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)); } }
看個官網的例子:
@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"); }
參考:
Mockito 初探
Mockito:一個強大的用於 Java 開發的模擬測試框架
Tags: 編寫程序 public double 數據庫 關鍵點
文章來源: