1. 程式人生 > >使用Mockito進行單元測試

使用Mockito進行單元測試

http://qiuguo0205.iteye.com/blog/1443344


1. 為什麼使用Mockito來進行單元測試?

 

回答這個問題需要回答兩個方面,第一個是為什麼使用mock?mock其實是一種工具的簡稱,他最大的功能是幫你把單元測試的耦合分解開,如果你的程式碼對另一個類或者介面有依賴,它能夠幫你模擬這些依賴,並幫你驗證所呼叫的依賴的行為。

 

比如一段程式碼有這樣的依賴:

 

 

當我們需要測試A類的時候,如果沒有mock,則我們需要把整個依賴樹都構建出來,而使用mock的話就可以將結構分解開,像下面這樣:

 

還有一個問題是mock工具那麼多,為什麼我們要用mockito呢?原因很簡單:他非常好用!

他使用執行後驗證的模型,語法更簡潔並且更加貼近程式設計師的思考方式,能夠模擬類而不僅僅是介面等等。總之如果你想使用mock的話,試用mockito,你不會後悔的:)

 

引用的圖摘自http://www.theserverside.com/news/1365050/Using-JMock-in-Test-Driven-Development,那裡對mock的使用有很好的介紹。

http://www.sizovpoint.com/2009/03/java-mock-frameworks-comparison.html

是一篇非常好的mock工具比較的文章,我就是從它認識的mockito,他也有對mock使用的精彩介紹。

還有一篇文章總結了mockito的好處:http://java.dzone.com/articles/mockito-pros-cons-and-best

 

當然,要想真正瞭解mockito的好處,就必須寫寫程式碼練習一下了。

 

2. Mockito使用例項

這裡的程式碼基本都是從http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html

摘出來的,然後加上了自己的一些學習驗證,這個網頁挺重要的,會多次提到,以後就簡稱”網頁“了。讓我們通過這些例項來看看mockito的強大功能吧:

 

1. 讓我們驗證一些行為吧

 

 

Java程式碼   收藏程式碼
  1. //Let's import Mockito statically so that the code looks clearer  
  2. import static org.mockito.Mockito.*;  
  3.   
  4. //mock creation  
  5. List mockedList = mock(List.class);  
  6.   
  7. // using mock object  
  8. mockedList.add("one");  
  9. mockedList.clear();  
  10. mockedList.add("3"); // no verify? OK  
  11.   
  12. // verification  
  13. verify(mockedList).add("one");  
  14. verify(mockedList).clear();  
  15. // verify(mockedList).add("2"); // this will throw an exception  
 

首先通過這段程式碼介紹什麼是mock:首先使用Mockito的靜態方法mock,我們就可以建立一個類的mock例項,這個mock例項擁有這個List的所有方法介面,並且給這些方法以最基本的實現:如果是返回void,他什麼都不做,否則他就返回null或0等基本型別的值。比如中間的三句呼叫了mock的方法,即使將來不驗證也沒有任何關係。

 

在驗證階段,當我們驗證這個mock的方法add("one")是否被呼叫的時候,他不會丟擲異常,因為我們確實呼叫了這個方法,但是當我們驗證它是否呼叫add("2")的時候,就會丟擲異常,說明我們沒有呼叫過這個方法,此時的測試就會失敗。

 

所以驗證的意思是”檢視我們到底有沒有呼叫過mock的這個方法“。

 

2. 它能提供樁[stub]測試嗎?

 

相信這樣的場景我們都遇到過,有一個方法的輸入是一個List,在這個方法中我們遍歷這個List,讀取資料,做相應的操作。往常我們可能需要自己建立一個ArrayList,並且將需要的測試的引數add進list中,這樣就可以分別進行測試了。下面看看使用mockito是怎麼做到的:

 

 

Java程式碼   收藏程式碼
  1. // You can mock concrete classes, not only interfaces  
  2. LinkedList mockedList = mock(LinkedList.class);  
  3.   
  4. // stubbing  
  5. when(mockedList.get(0)).thenReturn("first");  
  6. when(mockedList.get(1)).thenThrow(new RuntimeException());  
  7.   
  8. // following prints "first"  
  9. System.out.println(mockedList.get(0));  
  10. // following throws runtime exception  
  11. System.out.println(mockedList.get(1));  
  12. // following prints "null" because get(999) was not stubbed  
  13. System.out.println(mockedList.get(999));  
  14.   
  15. // Although it is possible to verify a stubbed invocation, usually it's just redundant  
  16. // See http://monkeyisland.pl/2008/04/26/asking-and-telling  
  17. verify(mockedList, atLeast(2)).get(0);  
 

首先我們可以看到mockito是可以mock類而不僅僅是介面的,而stub的語法也非常接近人的閱讀習慣:when(mockedList.get(0)).thenReturn("first"); 當呼叫get(0)的時候返回"first"。

 

這裡需要注意以下幾點:

【1】mock例項預設的會給所有的方法新增基本實現:返回null或空集合,或者0等基本型別的值。

【2】當我們連續兩次為同一個方法使用stub的時候,他只會只用最新的一次。

【3】一旦這個方法被stub了,就會一直返回這個stub的值。

像下面這段程式碼,你猜會列印什麼?

 

 

Java程式碼   收藏程式碼
  1. when(mockedList.get(0)).thenReturn("first");  
  2. when(mockedList.get(0)).thenReturn("oops");  
  3. System.out.println(mockedList.get(0));  
  4. System.out.println(mockedList.get(0));  

  3. 引數匹配

下面我們看看mockito強大的引數匹配機制,當mockito執行verify的時候,它實際上對引數執行的是自然地java方式——equals方法。有事我們需要對引數進行靈活匹配的時候就可以用到”引數匹配器“【argument matchers】了

 

 

Java程式碼   收藏程式碼
  1. // stubbing using built-in anyInt() argument matcher  
  2. when(mockedList.get(anyInt())).thenReturn("element");  
  3.   
  4. // following prints "element"  
  5. System.out.println(mockedList.get(999));  
  6.   
  7. // you can also verify using an argument matcher  
  8. verify(mockedList).get(anyInt());  
 

這裡的anyInt是mockito內建的眾多方法之一,其他可以參考mockito主頁上的資訊,你也可以呼叫hamcrest的matchers。

 

警告:若方法中的某一個引數使用了matcher,則所有的引數都必須使用matcher:

 

 

Java程式碼   收藏程式碼
  1. // correct  
  2. verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));  
  3. // will throw exception  
  4. verify(mock).someMethod(anyInt(), anyString(), "third argument");  

  4. 繼續討論Verification

 

前面的例子都是和網頁上的例子一一對應的,現在我們集中討論一下mockito在verify上提供的強大功能,大部分例子都很簡單,所以我基本就是簡單的羅列:

 

# 驗證方法被呼叫的次數 網頁例子4

 

Java程式碼   收藏程式碼
  1. //using mock   
  2. mockedList.add("once");  
  3.   
  4. mockedList.add("twice");  
  5. mockedList.add("twice");  
  6.   
  7. mockedList.add("three times");  
  8. mockedList.add("three times");  
  9. mockedList.add("three times");  
  10.   
  11. //following two verifications work exactly the same - times(1) is used by default  
  12. verify(mockedList).add("once");  
  13. verify(mockedList, times(1)).add("once");  
  14.   
  15. //exact number of invocations verification  
  16. verify(mockedList, times(2)).add("twice");  
  17. verify(mockedList, times(3)).add("three times");  
  18.   
  19. //verification using never(). never() is an alias to times(0)  
  20. verify(mockedList, never()).add("never happened");  
  21.   
  22. //verification using atLeast()/atMost()  
  23. verify(mockedList, atLeastOnce()).add("three times");  
  24. verify(mockedList, atLeast(2)).add("five times");  
  25. verify(mockedList, atMost(5)).add("three times");  
 

# 按順序驗證  網頁例子6

 

 

Java程式碼   收藏程式碼
  1. // A. Single mock whose methods must be invoked in a particular order  
  2. List singleMock = mock(List.class);  
  3.   
  4. //using a single mock  
  5. singleMock.add("was added first");  
  6. singleMock.add("was added second");  
  7.   
  8. //create an inOrder verifier for a single mock  
  9. InOrder inOrder = inOrder(singleMock);  
  10.   
  11. //following will make sure that add is first called with "was added first, then with "was added second"  
  12. inOrder.verify(singleMock).add("was added first");  
  13. inOrder.verify(singleMock).add("was added second");  
  14.   
  15. // B. Multiple mocks that must be used in a particular order  
  16. List firstMock = mock(List.class);  
  17. List secondMock = mock(List.class);  
  18.   
  19. //using mocks  
  20. firstMock.add("was called first");  
  21. secondMock.add("was called second");  
  22.   
  23. //create inOrder object passing any mocks that need to be verified in order  
  24. InOrder inOrder = inOrder(firstMock, secondMock);  
  25.   
  26. //following will make sure that firstMock was called before secondMock  
  27. inOrder.verify(firstMock).add("was called first");  
  28. inOrder.verify(secondMock).add("was called second");  
  29.   
  30. // Oh, and A + B can be mixed together at will  
 

# 確保某些方法沒有被呼叫  網頁例子7

 

 

Java程式碼   收藏程式碼
  1. //using mocks - only mockOne is interacted  
  2. mockOne.add("one");  
  3.   
  4. //ordinary verification  
  5. verify(mockOne).add("one");  
  6.   
  7. //verify that method was never called on a mock  
  8. verify(mockOne, never()).add("two");  
  9.   
  10. //verify that other mocks were not interacted  
  11. verifyZeroInteractions(mockTwo, mockThree);  
 

# 從前面的例子我們可以看到,能夠很容易地找到冗餘的呼叫  網頁例子8

 

 

Java程式碼   收藏程式碼
  1. //using mocks  
  2. mockedList.add("one");  
  3. mockedList.add("two");  
  4.   
  5. verify(mockedList).add("one");  
  6.   
  7. //following verification will fail   
  8. verifyNoMoreInteractions(mockedList);  
 

 

OK,看過Mockito的 mock 和 verify的能力,你可能已經喜歡上Mockito了,不過這只是Mockito強大功能的一部分,下一篇接著翻譯我個人用的最多的stub的功能,真的不可錯過,看完之後你絕對能夠驚歎Mockito的實力的;-)





一篇中介紹了Mockito的基本資訊,現在接著介紹Mockito強大的stub功能

 

2. Mockito使用例項

5. 對連續的呼叫進行不同的返回 (iterator-style stubbing)

還記得在例項2中說道當我們連續兩次為同一個方法使用stub的時候,他只會使用最新的一次。但是在某一個方法中我們確實有很多的呼叫怎麼辦呢?mockito當然想到這一點了:

 

 

Java程式碼   收藏程式碼
  1. when(mock.someMethod("some arg"))  
  2.   .thenThrow(new RuntimeException())  
  3.   .thenReturn("foo");  
  4.   
  5. //First call: throws runtime exception:  
  6. mock.someMethod("some arg");  
  7.   
  8. //Second call: prints "foo"  
  9. System.out.println(mock.someMethod("some arg"));  
  10.   
  11. //Any consecutive call: prints "foo" as well (last stubbing wins).   
  12. System.out.println(mock.someMethod("some arg"));  
 

 

當然我們也可以將第一句寫的更簡單一些:

 

Java程式碼   收藏程式碼
  1. when(mock.someMethod("some arg"))  
  2.   .thenReturn("one""two""three");  

參見網頁例子10。

 

6. 使用回撥進行stub【Stubbing with callbacks】

 

我們可以使用generic的Answer介面來讓mock物件執行我們期望它執行的內容。比如我們可以檢視呼叫方法的引數資訊,並根據這個資訊進行不同的處理,這可以使我們的stub變得十分的靈活。

 

 

Java程式碼   收藏程式碼
  1. when(mock.someMethod(anyString())).thenAnswer(new Answer() {  
  2.     Object answer(InvocationOnMock invocation) {  
  3.         Object[] args = invocation.getArguments();&nb