前言
Mockito是一個非常不錯的模擬框架,它使您可以使用簡潔的API編寫漂亮的測試。
介紹
本文將展示模擬框架的一些基本概念,爲什麼我們應該使用它,並逐步介紹一下在Java中應用Mockito的簡單方法。
mocking的概念
在軟件開發領域之外,術語“ mock
”表示模仿。mock
因此,可以被認爲是替身,冒名頂替者或與軟件開發有關的最常見稱呼 fake
。
僞造通常用作受測類依賴項的替身。
| 術語和定義 | | ------------------------------------------------------------ | | 依賴關係 –依賴關係是指應用程序中的一個類爲了執行其預期功能而依賴於另一個類。依賴關係通常存儲在依賴類內部的實例變量中。| | | | 被測類 –編寫單元測試時,術語“單元”通常是指單個類,尤其是針對其編寫測試的類。因此,被測類是被測應用程序類。|
爲什麼要模擬?
當我們學習編程時,我們的對象通常是獨立的。任何動作都不依賴外部類(除了System.out),在學習語言的過程中我們編寫的許多其他類也沒有依賴。但是,在現實世界中,軟件具有依賴性。我們有依賴於服務的操作類和依賴於數據訪問對象(DAO)的服務,並且列表繼續存在。
單元測試的思想是我們要測試代碼而不測試依賴項。該測試使您可以驗證所測試的代碼是否有效,無論其是否依賴。從理論上講,如果我編寫的代碼按設計工作,而我的依賴項按設計工作,那麼它們應該按設計一起工作。下面的代碼將是一個示例:
1. `import java.util.ArrayList;`
2. `publicclassCounter{`
3. `publicCounter() {`
4. `}`
5. `publicint count(ArrayList items) {`
6. `int results = 0;`
7. `for(Object curItem : items) {`
8. `results ++;`
9. `}`
10. `return results;`
11. `}`
12. `}`
我知道上面的示例與您所獲得的一樣簡單,但是它說明了這一點。如果要測試計數方法,你可以編寫測試用例。您並不是要測試ArrayList的工作原理,您唯一的目標是測試對ArrayList的使用。
模擬背後的概念是我們要創建一個代替真實對象的模擬對象。對該模擬對象調用某些方法,它將能返回期望的結果。
模擬概念是什麼?
當涉及到mocking時,您只需要關心三件事:存根,設定期望並進行驗證。 一些單元測試方案不涉及其中任何一個,其他僅涉及存根,而其他涉及設置期望和驗證。
存根
存根是告訴您的假貨與之互動時如何表現的過程。通常,您可以對公共屬性(具有getter和/或setter的屬性)和公共功能進行存根。
當涉及到存根函數時,通常會有很多選擇。您可能希望返回特定的值,拋出錯誤或調度事件。此外,您可能希望指出函數的行爲取決於調用方式的不同(即,通過匹配傳遞給函數的參數的類型或值)。
如果聽起來需要做很多工作,可以,但通常不是。許多模擬框架的一大特點是您不需要對void函數進行存根。您也不必在測試執行過程中對任何未調用的功能或未查詢的屬性進行存根。
設定期望
僞造品的主要功能之一是能夠在測試運行時告訴僞造品您期望的內容。例如,您可能希望特定的函數被正確調用3次。您可能希望它永遠不會被調用。您可能希望至少調用兩次,但不要超過5次。您可能希望使用特定類型的參數或特定值或以上任意組合來調用它。可能性是無止境。
設定期望是告訴您的假貨您預期會發生什麼的過程。請記住,由於這是假貨,因此實際上沒有任何反應。但是,您正在測試的課程絕不明智。從它的角度來看,它調用了該函數並期望它完成了應該執行的操作。
值得一提的是,大多數模擬框架都允許您創建接口或公共類的模擬。您不僅限於僅模擬接口。
驗證中
設定期望和驗證是齊頭並進的。設置期望值是在調用被測類上的函數之前完成的。之後進行驗證。因此,首先要設定期望,然後驗證是否滿足期望。
從單元測試的角度來看,如果未達到您的期望,則單元測試將失敗。例如,如果您期望應該使用特定的用戶名和密碼僅一次調用ILoginService.login函數,但是在執行測試期間從未調用過該函數,則僞造品將無法驗證,並且測試應失敗。
mocking有什麼好處?
**
您可以預先創建測試;TDD**
這是更強大的好處之一。如果創建了Mock,則可以在創建服務之前編寫服務測試,從而使您能夠在開發過程中將測試添加到自動化環境中。換句話說,服務模擬使您能夠使用測試驅動開發。
團隊可以並行工作
這類似於上面的內容;爲不存在的代碼創建測試。但是上一點是針對編寫測試的開發人員的,這一點是針對測試團隊的。當您沒有要測試的東西時,團隊如何開始創建測試?模擬它並針對模擬編寫測試!這意味着質量保證團隊實際上可以在準備好要測試服務時準備一整套測試。當一個團隊等待另一個團隊完成時,我們沒有停機時間。這使得嘲弄的財務論點特別強烈。
您可以創建概念或演示的證明。
由於Mocks可以(非常容易地完成)製造,因此非常經濟高效,因此可以將Mocks用於創建概念驗證,線框或正在考慮構建的產品的演示。這非常強大,爲決策是否繼續進行開發項目提供了良好的基礎,但最重要的是可以進行實際的設計決策。
您可以爲無法訪問的資源編寫測試
這是不屬於實際福利類別的福利之一,而是可以起到挽救生命的作用。您是否曾經想測試或使用一項服務,卻只是被告知該服務位於防火牆後面,並且無法爲您打開該防火牆或您已被授權使用該服務?當您這樣做時,將MockService放置在可訪問的位置(包括您的本地計算機上)可以節省生命。
模擬可以交付給客戶
在某些情況下,有某些原因導致您不允許外部資源(例如合作伙伴或客戶)訪問測試系統。這些原因可能是訪問安全性,信息的敏感性,或者僅僅是測試環境可能無法24/7全天候訪問的事實。在這些情況下;您如何爲合作伙伴或客戶提供測試系統以開始開發或測試?一個簡單的解決方案是從您的網絡或客戶自己的網絡提供模擬。soapUI模擬程序非常易於部署,它既可以在soapUI中運行,也可以作爲.WAR文件導出並放置在您選擇的Java服務器中。
您可以隔離系統
有時,您希望測試系統的一部分,而不會影響其他系統部分。這是因爲其他系統會給測試數據增加噪音,並使從收集到的數據中得出良好的結論變得更加困難。使用模擬,您可以刪除模擬所有系統的所有依賴項,但您需要在測試中精確定位的系統除外。在進行模擬隔離時,可以使這些模擬極其簡單,但可靠,快速且可預測。這爲您提供了一個測試環境,在其中消除了所有隨機行爲,具有可重複的模式並可以很好地監視特定系統。
Mockito框架
Mockito是根據MIT許可證發佈的Java開源測試框架。Mockito通過允許開發人員驗證被測系統(SUT)的行爲而無需事先建立期望,從而將自己與其他模擬框架區分開。[4] 對模擬對象的批評之一是測試代碼與被測系統之間的耦合更加緊密。[5] 由於Mockito試圖通過消除期望值的規範來消除期望-運行-驗證模式[6],因此減少或最小化了耦合。此區別功能的結果是更簡單的測試代碼,應該更易於閱讀和修改。
您可以驗證交互:
1. `// mock creation`
2. `List mockedList = mock(List.class);`
3. `<span class="Apple-tab-span" style="white-space: pre;"> `
4. `</span>// using mock object`
5. `mockedList.add("one");`
6. `mockedList.clear();`
7. `// selective and explicit vertification`
8. `verify(mockedList).add("one");`
9. `verify(mockedList).clear(); `
或存根方法調用
1. `// you can mock concrete class, not only interfaces`
2. `LinkedList mockedList = mock(LinkedList.class);`
3. `<span class="Apple-tab-span" style="white-space: pre;"> `
4. `</span>// stubbing - before execution`
5. `when(mockedList.get(0)).thenReturn("first");..`
6. `<span class="Apple-tab-span" style="white-space: pre;"> `
7. `</span>// following prints "first"`
8. `System.out.println(mockedList.get(0));`
9. `<span class="Apple-tab-span" style="white-space: pre;"> `
10. `</span>// following prints "null" because get(999) was not stubbed.`
11. `System.out.println(mockedList.get(999));`
一個使用Mockito的簡單Java代碼示例
沒有模擬框架
使用Mockito框架
步驟1:在Eclipse中創建一個Maven項目
定義 pom.xml
如下:
1. `<?xml version="1.0" encoding="UTF-8"?>`
2. `<pre><projectxmlns="http://maven.apache.org/POM/4.0.0"`
3. `xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`
4. `xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">`
5. `<modelVersion>4.0.0</modelVersion>`
6. `<groupId>vn.com.phatbeo.ut.mockito.demo</groupId>`
7. `<artifactId>demoMockito</artifactId>`
8. `<version>0.0.1-SNAPSHOT</version>`
9. `<packaging>jar</packaging>`
10. `<name>demoMockito</name>`
11. `<url>http://maven.apache.org</url>`
12. `<properties>`
13. `<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>`
14. `</properties>`
15. `<build>`
16. `<sourceDirectory>src</sourceDirectory>`
17. `<testSourceDirectory>test</testSourceDirectory>`
18. `<plugins>`
19. `<plugin>`
20. `<groupId>org.apache.maven.plugins</groupId>`
21. `<artifactId>maven-compiler-plugin</artifactId>`
22. `<version>2.3.1</version>`
23. `<configuration>`
24. `<source>1.6</source>`
25. `<target>1.6</target>`
26. `</configuration>`
27. `</plugin>`
28. `</plugins>`
29. `</build>`
30. `<dependencies>`
31. `<dependency>`
32. `<groupId>junit</groupId>`
33. `<artifactId>junit</artifactId>`
34. `<version>4.8.1</version>`
35. `<scope>test</scope>`
36. `</dependency>`
37. `<dependency>`
38. `<groupId>org.mockito</groupId>`
39. `<artifactId>mockito-all</artifactId>`
40. `<version>1.8.5</version>`
41. `<scope>test</scope>`
42. `</dependency>`
43. `</dependencies>`
44. `</project>`
步驟2:添加Java源代碼
類 Person.java
1. `package vn.com.enclave.phatbeo.ut.mockito.demo;`
2. `/**`
3. `* @author Phat (Phillip) H. VU <[email protected]>`
4. `*`
5. `*/`
6. `publicclassPerson`
7. `{`
8. `privatefinalInteger personID;`
9. `privatefinalString personName;`
10. `publicPerson( Integer personID, String personName )`
11. `{`
12. `this.personID = personID;`
13. `this.personName = personName;`
14. `}`
15. `publicInteger getPersonID()`
16. `{`
17. `return personID;`
18. `}`
19. `publicString getPersonName()`
20. `{`
21. `return personName;`
22. `}`
23. `}`
接口 PersonDAO.java
1. `package vn.com.enclave.phatbeo.ut.mockito.demo;`
2. `/**`
3. `* @author Phat (Phillip) H. VU <[email protected]>`
4. `*`
5. `*/`
6. `publicinterfacePersonDao`
7. `{`
8. `publicPerson fetchPerson( Integer personID );`
9. `publicvoid update( Person person );`
10. `}`
類 PersonService.java
1. `package vn.com.enclave.phatbeo.ut.mockito.demo;`
2. `/**`
3. `* @author Phat (Phillip) H. VU <[email protected]>`
4. `*`
5. `*/`
6. `publicclassPersonService`
7. `{`
8. `privatefinalPersonDao personDao;`
9. `publicPersonService( PersonDao personDao )`
10. `{`
11. `this.personDao = personDao;`
12. `}`
13. `publicboolean update( Integer personId, String name )`
14. `{`
15. `Person person = personDao.fetchPerson( personId );`
16. `if( person != null)`
17. `{`
18. `Person updatedPerson = newPerson( person.getPersonID(), name );`
19. `personDao.update( updatedPerson );`
20. `returntrue;`
21. `}`
22. `else`
23. `{`
24. `returnfalse;`
25. `}`
26. `}`
27. `}`
步驟3:添加了單元測試類。
然後,跳轉爲類編寫單元測試用例 PersonService.java
假設類 PersionServiceTest.java
如下:
1. `package vn.com.enclave.phatbeo.ut.mockito.demo.test;`
2. `importstatic org.junit.Assert.assertEquals;`
3. `importstatic org.junit.Assert.assertFalse;`
4. `importstatic org.junit.Assert.assertTrue;`
5. `importstatic org.mockito.Mockito.verify;`
6. `importstatic org.mockito.Mockito.verifyNoMoreInteractions;`
7. `importstatic org.mockito.Mockito.verifyZeroInteractions;`
8. `importstatic org.mockito.Mockito.when;`
9. `import org.junit.Before;`
10. `import org.junit.Test;`
11. `import org.mockito.ArgumentCaptor;`
12. `import org.mockito.Mock;`
13. `import org.mockito.MockitoAnnotations;`
14. `/**`
15. `* @author Phat (Phillip) H. VU <[email protected]>`
16. `*`
17. `*/`
18. `publicclassPersonServiceTest`
19. `{`
20. `@Mock`
21. `privatePersonDao personDAO;`
22. `privatePersonService personService;`
23. `@Before`
24. `publicvoid setUp()`
25. `throwsException`
26. `{`
27. `MockitoAnnotations.initMocks( this);`
28. `personService = newPersonService( personDAO );`
29. `}`
30. `@Test`
31. `publicvoid shouldUpdatePersonName()`
32. `{`
33. `Person person = newPerson( 1, "Phillip");`
34. `when( personDAO.fetchPerson( 1) ).thenReturn( person );`
35. `boolean updated = personService.update( 1, "David");`
36. `assertTrue( updated );`
37. `verify( personDAO ).fetchPerson( 1);`
38. `ArgumentCaptor<Person> personCaptor = ArgumentCaptor.forClass( Person.class);`
39. `verify( personDAO ).update( personCaptor.capture() );`
40. `Person updatedPerson = personCaptor.getValue();`
41. `assertEquals( "David", updatedPerson.getPersonName() );`
42. `// asserts that during the test, there are no other calls to the mock object.`
43. `verifyNoMoreInteractions( personDAO );`
44. `}`
45. `@Test`
46. `publicvoid shouldNotUpdateIfPersonNotFound()`
47. `{`
48. `when( personDAO.fetchPerson( 1) ).thenReturn( null);`
49. `boolean updated = personService.update( 1, "David");`
50. `assertFalse( updated );`
51. `verify( personDAO ).fetchPerson( 1);`
52. `verifyZeroInteractions( personDAO );`
53. `verifyNoMoreInteractions( personDAO );`
54. `}`
55. `}`
問題:爲什麼我們在Java開發測試中使用Mockito?
答:好處有很多,最大的好處是團隊可以並行工作