使用强大的 Mockito 测试框架来测试你的代码
這篇教程介紹了如何使用 Mockito 框架來給軟件寫測(cè)試用例
1. 預(yù)備知識(shí)
如果需要往下學(xué)習(xí),你需要先理解 Junit 框架中的單元測(cè)試。
如果你不熟悉 JUnit,請(qǐng)查看下面的教程:?http://www.vogella.com/tutorials/JUnit/article.html
2. 使用mock對(duì)象來進(jìn)行測(cè)試
2.1. 單元測(cè)試的目標(biāo)和挑戰(zhàn)
單元測(cè)試的思路是在不涉及依賴關(guān)系的情況下測(cè)試代碼(隔離性),所以測(cè)試代碼與其他類或者系統(tǒng)的關(guān)系應(yīng)該盡量被消除。一個(gè)可行的消除方法是替換掉依賴類(測(cè)試替換),也就是說我們可以使用替身來替換掉真正的依賴對(duì)象。
2.2. 測(cè)試類的分類
dummy object?做為參數(shù)傳遞給方法但是絕對(duì)不會(huì)被使用。譬如說,這種測(cè)試類內(nèi)部的方法不會(huì)被調(diào)用,或者是用來填充某個(gè)方法的參數(shù)。
Fake?是真正接口或抽象類的實(shí)現(xiàn)體,但給對(duì)象內(nèi)部實(shí)現(xiàn)很簡(jiǎn)單。譬如說,它存在內(nèi)存中而不是真正的數(shù)據(jù)庫中。(譯者注:Fake?實(shí)現(xiàn)了真正的邏輯,但它的存在只是為了測(cè)試,而不適合于用在產(chǎn)品中。)
stub?類是依賴類的部分方法實(shí)現(xiàn),而這些方法在你測(cè)試類和接口的時(shí)候會(huì)被用到,也就是說?stub?類在測(cè)試中會(huì)被實(shí)例化。stub?類會(huì)回應(yīng)任何外部測(cè)試的調(diào)用。stub?類有時(shí)候還會(huì)記錄調(diào)用的一些信息。
mock object?是指類或者接口的模擬實(shí)現(xiàn),你可以自定義這個(gè)對(duì)象中某個(gè)方法的輸出結(jié)果。
測(cè)試替代技術(shù)能夠在測(cè)試中模擬測(cè)試類以外對(duì)象。因此你可以驗(yàn)證測(cè)試類是否響應(yīng)正常。譬如說,你可以驗(yàn)證在 Mock 對(duì)象的某一個(gè)方法是否被調(diào)用。這可以確保隔離了外部依賴的干擾只測(cè)試測(cè)試類。
我們選擇 Mock 對(duì)象的原因是因?yàn)?Mock 對(duì)象只需要少量代碼的配置。
2.3. Mock 對(duì)象的產(chǎn)生
你可以手動(dòng)創(chuàng)建一個(gè) Mock 對(duì)象或者使用 Mock 框架來模擬這些類,Mock 框架允許你在運(yùn)行時(shí)創(chuàng)建 Mock 對(duì)象并且定義它的行為。
一個(gè)典型的例子是把 Mock 對(duì)象模擬成數(shù)據(jù)的提供者。在正式的生產(chǎn)環(huán)境中它會(huì)被實(shí)現(xiàn)用來連接數(shù)據(jù)源。但是我們?cè)跍y(cè)試的時(shí)候 Mock 對(duì)象將會(huì)模擬成數(shù)據(jù)提供者來確保我們的測(cè)試環(huán)境始終是相同的。
Mock 對(duì)象可以被提供來進(jìn)行測(cè)試。因此,我們測(cè)試的類應(yīng)該避免任何外部數(shù)據(jù)的強(qiáng)依賴。
通過 Mock 對(duì)象或者 Mock 框架,我們可以測(cè)試代碼中期望的行為。譬如說,驗(yàn)證只有某個(gè)存在 Mock 對(duì)象的方法是否被調(diào)用了。
2.4. 使用 Mockito 生成 Mock 對(duì)象
Mockito?是一個(gè)流行 mock 框架,可以和JUnit結(jié)合起來使用。Mockito 允許你創(chuàng)建和配置 mock 對(duì)象。使用Mockito可以明顯的簡(jiǎn)化對(duì)外部依賴的測(cè)試類的開發(fā)。
一般使用 Mockito 需要執(zhí)行下面三步
-
模擬并替換測(cè)試代碼中外部依賴。
-
執(zhí)行測(cè)試代碼
-
驗(yàn)證測(cè)試代碼是否被正確的執(zhí)行
3. 為自己的項(xiàng)目添加 Mockito 依賴
3.1. 在 Gradle 添加 Mockito 依賴
如果你的項(xiàng)目使用 Gradle 構(gòu)建,將下面代碼加入 Gradle 的構(gòu)建文件中為自己項(xiàng)目添加 Mockito 依賴
repositories { jcenter() } dependencies { testCompile "org.mockito:mockito-core:2.0.57-beta" }3.2. 在 Maven 添加 Mockito 依賴
需要在 Maven 聲明依賴,您可以在?http://search.maven.org?網(wǎng)站中搜索 g:"org.mockito", a:"mockito-core" 來得到具體的聲明方式。
3.3. 在 Eclipse IDE 使用 Mockito
Eclipse IDE 支持 Gradle 和 Maven 兩種構(gòu)建工具,所以在 Eclipse IDE 添加依賴取決你使用的是哪一個(gè)構(gòu)建工具。
3.4. 以 OSGi 或者 Eclipse 插件形式添加 Mockito 依賴
在 Eclipse RCP 應(yīng)用依賴通常可以在 p2 update 上得到。Orbit 是一個(gè)很好的第三方倉庫,我們可以在里面尋找能在 Eclipse 上使用的應(yīng)用和插件。
Orbit 倉庫地址?http://download.eclipse.org/tools/orbit/downloads
4. 使用Mockito API
4.1. 靜態(tài)引用
如果在代碼中靜態(tài)引用了org.mockito.Mockito.*;,那你你就可以直接調(diào)用靜態(tài)方法和靜態(tài)變量而不用創(chuàng)建對(duì)象,譬如直接調(diào)用 mock() 方法。
4.2. 使用 Mockito 創(chuàng)建和配置 mock 對(duì)象
除了上面所說的使用 mock() 靜態(tài)方法外,Mockito 還支持通過?@Mock?注解的方式來創(chuàng)建 mock 對(duì)象。
如果你使用注解,那么必須要實(shí)例化 mock 對(duì)象。Mockito 在遇到使用注解的字段的時(shí)候,會(huì)調(diào)用MockitoAnnotations.initMocks(this)?來初始化該 mock 對(duì)象。另外也可以通過使用@RunWith(MockitoJUnitRunner.class)來達(dá)到相同的效果。
通過下面的例子我們可以了解到使用@Mock?的方法和MockitoRule規(guī)則。
import static org.mockito.Mockito.*;public class MockitoTest {@MockMyDatabase databaseMock; (1)@Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); (2)@Testpublic void testQuery() {ClassToTest t = new ClassToTest(databaseMock); (3)boolean check = t.query("* from t"); (4)assertTrue(check); (5)verify(databaseMock).query("* from t"); (6)} }告訴 Mockito 模擬 databaseMock 實(shí)例
Mockito 通過 @mock 注解創(chuàng)建 mock 對(duì)象
使用已經(jīng)創(chuàng)建的mock初始化這個(gè)類
在測(cè)試環(huán)境下,執(zhí)行測(cè)試類中的代碼
使用斷言確保調(diào)用的方法返回值為 true
驗(yàn)證 query 方法是否被?MyDatabase?的 mock 對(duì)象調(diào)用
4.3. 配置 mock
當(dāng)我們需要配置某個(gè)方法的返回值的時(shí)候,Mockito 提供了鏈?zhǔn)降?API 供我們方便的調(diào)用
when(…?.).thenReturn(…?.)可以被用來定義當(dāng)條件滿足時(shí)函數(shù)的返回值,如果你需要定義多個(gè)返回值,可以多次定義。當(dāng)你多次調(diào)用函數(shù)的時(shí)候,Mockito 會(huì)根據(jù)你定義的先后順序來返回返回值。Mocks 還可以根據(jù)傳入?yún)?shù)的不同來定義不同的返回值。譬如說你的函數(shù)可以將anyString?或者?anyInt作為輸入?yún)?shù),然后定義其特定的放回值。
import static org.mockito.Mockito.*; import static org.junit.Assert.*;@Test public void test1() {// 創(chuàng)建 mockMyClass test = Mockito.mock(MyClass.class);// 自定義 getUniqueId() 的返回值when(test.getUniqueId()).thenReturn(43);// 在測(cè)試中使用mock對(duì)象assertEquals(test.getUniqueId(), 43); }// 返回多個(gè)值 @Test public void testMoreThanOneReturnValue() {Iterator i= mock(Iterator.class);when(i.next()).thenReturn("Mockito").thenReturn("rocks");String result=i.next()+" "+i.next();// 斷言assertEquals("Mockito rocks", result); }// 如何根據(jù)輸入來返回值 @Test public void testReturnValueDependentOnMethodParameter() {Comparable c= mock(Comparable.class);when(c.compareTo("Mockito")).thenReturn(1);when(c.compareTo("Eclipse")).thenReturn(2);// 斷言assertEquals(1,c.compareTo("Mockito")); }// 如何讓返回值不依賴于輸入 @Test public void testReturnValueInDependentOnMethodParameter() {Comparable c= mock(Comparable.class);when(c.compareTo(anyInt())).thenReturn(-1);// 斷言assertEquals(-1 ,c.compareTo(9)); }// 根據(jù)參數(shù)類型來返回值 @Test public void testReturnValueInDependentOnMethodParameter() {Comparable c= mock(Comparable.class);when(c.compareTo(isA(Todo.class))).thenReturn(0);// 斷言Todo todo = new Todo(5);assertEquals(todo ,c.compareTo(new Todo(1))); }對(duì)于無返回值的函數(shù),我們可以使用doReturn(…?).when(…?).methodCall來獲得類似的效果。例如我們想在調(diào)用某些無返回值函數(shù)的時(shí)候拋出異常,那么可以使用doThrow?方法。如下面代碼片段所示
import static org.mockito.Mockito.*; import static org.junit.Assert.*;// 下面測(cè)試用例描述了如何使用doThrow()方法@Test(expected=IOException.class) public void testForIOException() {// 創(chuàng)建并配置 mock 對(duì)象OutputStream mockStream = mock(OutputStream.class);doThrow(new IOException()).when(mockStream).close();// 使用 mockOutputStreamWriter streamWriter= new OutputStreamWriter(mockStream);streamWriter.close(); }4.4. 驗(yàn)證 mock 對(duì)象方法是否被調(diào)用
Mockito 會(huì)跟蹤 mock 對(duì)象里面所有的方法和變量。所以我們可以用來驗(yàn)證函數(shù)在傳入特定參數(shù)的時(shí)候是否被調(diào)用。這種方式的測(cè)試稱行為測(cè)試,行為測(cè)試并不會(huì)檢查函數(shù)的返回值,而是檢查在傳入正確參數(shù)時(shí)候函數(shù)是否被調(diào)用。
import static org.mockito.Mockito.*;@Test public void testVerify() {// 創(chuàng)建并配置 mock 對(duì)象MyClass test = Mockito.mock(MyClass.class);when(test.getUniqueId()).thenReturn(43);// 調(diào)用mock對(duì)象里面的方法并傳入?yún)?shù)為12test.testing(12);test.getUniqueId();test.getUniqueId();// 查看在傳入?yún)?shù)為12的時(shí)候方法是否被調(diào)用verify(test).testing(Matchers.eq(12));// 方法是否被調(diào)用兩次verify(test, times(2)).getUniqueId();// 其他用來驗(yàn)證函數(shù)是否被調(diào)用的方法verify(mock, never()).someMethod("never called");verify(mock, atLeastOnce()).someMethod("called at least once");verify(mock, atLeast(2)).someMethod("called at least twice");verify(mock, times(5)).someMethod("called five times");verify(mock, atMost(3)).someMethod("called at most 3 times"); }4.5. 使用 Spy 封裝 java 對(duì)象
@Spy或者spy()方法可以被用來封裝 java 對(duì)象。被封裝后,除非特殊聲明(打樁?stub),否則都會(huì)真正的調(diào)用對(duì)象里面的每一個(gè)方法
import static org.mockito.Mockito.*;// Lets mock a LinkedList List list = new LinkedList(); List spy = spy(list);// 可用 doReturn() 來打樁 doReturn("foo").when(spy).get(0);// 下面代碼不生效 // 真正的方法會(huì)被調(diào)用 // 將會(huì)拋出 IndexOutOfBoundsException 的異常,因?yàn)?List 為空 when(spy.get(0)).thenReturn("foo");方法verifyNoMoreInteractions()允許你檢查沒有其他的方法被調(diào)用了。
4.6. 使用 @InjectMocks 在 Mockito 中進(jìn)行依賴注入
我們也可以使用@InjectMocks?注解來創(chuàng)建對(duì)象,它會(huì)根據(jù)類型來注入對(duì)象里面的成員方法和變量。假定我們有 ArticleManager 類
public class ArticleManager {private User user;private ArticleDatabase database;ArticleManager(User user) {this.user = user;}void setDatabase(ArticleDatabase database) { } }這個(gè)類會(huì)被 Mockito 構(gòu)造,而類的成員方法和變量都會(huì)被 mock 對(duì)象所代替,正如下面的代碼片段所示:
@RunWith(MockitoJUnitRunner.class) public class ArticleManagerTest {@Mock ArticleCalculator calculator;@Mock ArticleDatabase database;@Most User user;@Spy private UserProvider userProvider = new ConsumerUserProvider();@InjectMocks private ArticleManager manager; (1)@Test public void shouldDoSomething() {// 假定 ArticleManager 有一個(gè)叫 initialize() 的方法被調(diào)用了// 使用 ArticleListener 來調(diào)用 addListener 方法manager.initialize();// 驗(yàn)證 addListener 方法被調(diào)用verify(database).addListener(any(ArticleListener.class));} }更多的詳情可以查看?http://docs.mockito.googlecode.com/hg/1.9.5/org/mockito/InjectMocks.html.
4.7. 捕捉參數(shù)
ArgumentCaptor類允許我們?cè)趘erification期間訪問方法的參數(shù)。得到方法的參數(shù)后我們可以使用它進(jìn)行測(cè)試。
import static org.hamcrest.Matchers.hasItem; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify;import java.util.Arrays; import java.util.List;import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule;public class MockitoTests {@Rule public MockitoRule rule = MockitoJUnit.rule();@Captorprivate ArgumentCaptor> captor;@Testpublic final void shouldContainCertainListItem() {List asList = Arrays.asList("someElement_test", "someElement");final List mockedList = mock(List.class);mockedList.addAll(asList);verify(mockedList).addAll(captor.capture());final List capturedArgument = captor.>getValue();assertThat(capturedArgument, hasItem("someElement"));} }4.8. Mockito的限制
Mockito當(dāng)然也有一定的限制。而下面三種數(shù)據(jù)類型則不能夠被測(cè)試
-
final classes
-
anonymous classes
-
primitive types
5. 在Android中使用Mockito
在 Android 中的 Gradle 構(gòu)建文件中加入 Mockito 依賴后就可以直接使用 Mockito 了。若想使用 Android Instrumented tests 的話,還需要添加 dexmaker 和 dexmaker-mockito 依賴到 Gradle 的構(gòu)建文件中。(需要 Mockito 1.9.5版本以上)
dependencies {testCompile 'junit:junit:4.12'// Mockito unit test 的依賴testCompile 'org.mockito:mockito-core:1.+'// Mockito Android instrumentation tests 的依賴androidTestCompile 'org.mockito:mockito-core:1.+'androidTestCompile "com.google.dexmaker:dexmaker:1.2"androidTestCompile "com.google.dexmaker:dexmaker-mockito:1.2" }6. 實(shí)例:使用Mockito寫一個(gè)Instrumented Unit Test
6.1. 創(chuàng)建一個(gè)測(cè)試的Android 應(yīng)用
創(chuàng)建一個(gè)包名為com.vogella.android.testing.mockito.contextmock的Android應(yīng)用,添加一個(gè)靜態(tài)方法 ,方法里面創(chuàng)建一個(gè)包含參數(shù)的Intent,如下代碼所示:
public static Intent createQuery(Context context, String query, String value) {// 簡(jiǎn)單起見,重用MainActivityIntent i = new Intent(context, MainActivity.class);i.putExtra("QUERY", query);i.putExtra("VALUE", value);return i; }6.2. 在app/build.gradle文件中添加Mockito依賴
dependencies {// Mockito 和 JUnit 的依賴// instrumentation unit tests on the JVMandroidTestCompile 'junit:junit:4.12'androidTestCompile 'org.mockito:mockito-core:2.0.57-beta'androidTestCompile 'com.android.support.test:runner:0.3'androidTestCompile "com.google.dexmaker:dexmaker:1.2"androidTestCompile "com.google.dexmaker:dexmaker-mockito:1.2"// Mockito 和 JUnit 的依賴// tests on the JVMtestCompile 'junit:junit:4.12'testCompile 'org.mockito:mockito-core:1.+'}6.3. 創(chuàng)建測(cè)試
使用 Mockito 創(chuàng)建一個(gè)單元測(cè)試來驗(yàn)證在傳遞正確 extra data 的情況下,intent 是否被觸發(fā)。
因此我們需要使用 Mockito 來 mock 一個(gè)Context對(duì)象,如下代碼所示:
package com.vogella.android.testing.mockitocontextmock;import android.content.Context; import android.content.Intent; import android.os.Bundle;import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito;import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull;public class TextIntentCreation {@Testpublic void testIntentShouldBeCreated() {Context context = Mockito.mock(Context.class);Intent intent = MainActivity.createQuery(context, "query", "value");assertNotNull(intent);Bundle extras = intent.getExtras();assertNotNull(extras);assertEquals("query", extras.getString("QUERY"));assertEquals("value", extras.getString("VALUE"));} }7. 實(shí)例:使用 Mockito 創(chuàng)建一個(gè) mock 對(duì)象
7.1. 目標(biāo)
創(chuàng)建一個(gè) Api,它可以被 Mockito 來模擬并做一些工作
7.2. 創(chuàng)建一個(gè)Twitter API 的例子
實(shí)現(xiàn)?TwitterClient類,它內(nèi)部使用到了?ITweet?的實(shí)現(xiàn)。但是ITweet實(shí)例很難得到,譬如說他需要啟動(dòng)一個(gè)很復(fù)雜的服務(wù)來得到。
public interface ITweet {String getMessage(); }public class TwitterClient {public void sendTweet(ITweet tweet) {String message = tweet.getMessage();// send the message to Twitter} }7.3. 模擬 ITweet 的實(shí)例
為了能夠不啟動(dòng)復(fù)雜的服務(wù)來得到?ITweet,我們可以使用 Mockito 來模擬得到該實(shí)例。
@Test public void testSendingTweet() {TwitterClient twitterClient = new TwitterClient();ITweet iTweet = mock(ITweet.class);when(iTweet.getMessage()).thenReturn("Using mockito is great");twitterClient.sendTweet(iTweet); }現(xiàn)在?TwitterClient?可以使用?ITweet?接口的實(shí)現(xiàn),當(dāng)調(diào)用?getMessage()?方法的時(shí)候?qū)?huì)打印 "Using Mockito is great" 信息。
7.4. 驗(yàn)證方法調(diào)用
確保 getMessage() 方法至少調(diào)用一次。
@Test public void testSendingTweet() {TwitterClient twitterClient = new TwitterClient();ITweet iTweet = mock(ITweet.class);when(iTweet.getMessage()).thenReturn("Using mockito is great");twitterClient.sendTweet(iTweet);verify(iTweet, atLeastOnce()).getMessage(); }7.5. 驗(yàn)證
運(yùn)行測(cè)試,查看代碼是否測(cè)試通過。
8. 模擬靜態(tài)方法
8.1. 使用 Powermock 來模擬靜態(tài)方法
因?yàn)?Mockito 不能夠 mock 靜態(tài)方法,因此我們可以使用?Powermock。
import java.net.InetAddress; import java.net.UnknownHostException;public final class NetworkReader {public static String getLocalHostname() {String hostname = "";try {InetAddress addr = InetAddress.getLocalHost();// Get hostnamehostname = addr.getHostName();} catch ( UnknownHostException e ) {}return hostname;} }我們模擬了 NetworkReader 的依賴,如下代碼所示:
import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest;@RunWith( PowerMockRunner.class ) @PrepareForTest( NetworkReader.class ) public class MyTest {// 測(cè)試代碼@Test public void testSomething() {mockStatic( NetworkUtil.class );when( NetworkReader.getLocalHostname() ).andReturn( "localhost" );// 與 NetworkReader 協(xié)作的測(cè)試 }8.2.用封裝的方法代替Powermock
有時(shí)候我們可以在靜態(tài)方法周圍包含非靜態(tài)的方法來達(dá)到和 Powermock 同樣的效果。
class FooWraper { void someMethod() { Foo.someStaticMethod() } }9. Mockito 參考資料
http://site.mockito.org?- Mockito 官網(wǎng)
https://github.com/mockito/mockito-?Mockito Github
https://github.com/mockito/mockito/blob/master/doc/release-notes/official.md?- Mockito 發(fā)行說明
http://martinfowler.com/articles/mocksArentStubs.html?與Mocks,Stub有關(guān)的文章
http://chiuki.github.io/advanced-android-espresso/?高級(jí)android教程(竟然是個(gè)妹子)
from:?https://github.com/xitu/gold-miner/blob/master/TODO/Unit-tests-with-Mockito.md
總結(jié)
以上是生活随笔為你收集整理的使用强大的 Mockito 测试框架来测试你的代码的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java的mock测试框架
- 下一篇: 防止重复提交表单