- 原文作者 : vogella
- 譯者 : edvardhua
- 校對者: hackerkevin, futureshine
這篇教程介紹了如何使用 Mockito 框架來給軟件寫測試用例
1. 預(yù)備知識
如果需要往下學(xué)習(xí)良价,你需要先理解 Junit 框架中的單元測試件豌。
如果你不熟悉 JUnit译打,請查看下面的教程:
http://www.vogella.com/tutorials/JUnit/article.html
2. 使用mock對象來進(jìn)行測試
2.1. 單元測試的目標(biāo)和挑戰(zhàn)
單元測試的思路是在不涉及依賴關(guān)系的情況下測試代碼(隔離性)宵呛,所以測試代碼與其他類或者系統(tǒng)的關(guān)系應(yīng)該盡量被消除。一個可行的消除方法是替換掉依賴類(測試替換)羽氮,也就是說我們可以使用替身來替換掉真正的依賴對象缘回。
2.2. 測試類的分類
dummy object 做為參數(shù)傳遞給方法但是絕對不會被使用。譬如說念脯,這種測試類內(nèi)部的方法不會被調(diào)用,或者是用來填充某個方法的參數(shù)弯淘。
Fake 是真正接口或抽象類的實(shí)現(xiàn)體绿店,但給對象內(nèi)部實(shí)現(xiàn)很簡單。譬如說庐橙,它存在內(nèi)存中而不是真正的數(shù)據(jù)庫中惯吕。(譯者注:Fake 實(shí)現(xiàn)了真正的邏輯惕它,但它的存在只是為了測試,而不適合于用在產(chǎn)品中废登。)
stub 類是依賴類的部分方法實(shí)現(xiàn),而這些方法在你測試類和接口的時候會被用到郁惜,也就是說 stub 類在測試中會被實(shí)例化堡距。stub 類會回應(yīng)任何外部測試的調(diào)用。stub 類有時候還會記錄調(diào)用的一些信息兆蕉。
mock object 是指類或者接口的模擬實(shí)現(xiàn)羽戒,你可以自定義這個對象中某個方法的輸出結(jié)果。
測試替代技術(shù)能夠在測試中模擬測試類以外對象虎韵。因此你可以驗(yàn)證測試類是否響應(yīng)正常易稠。譬如說,你可以驗(yàn)證在 Mock 對象的某一個方法是否被調(diào)用包蓝。這可以確保隔離了外部依賴的干擾只測試測試類驶社。
我們選擇 Mock 對象的原因是因?yàn)?Mock 對象只需要少量代碼的配置。
2.3. Mock 對象的產(chǎn)生
你可以手動創(chuàng)建一個 Mock 對象或者使用 Mock 框架來模擬這些類测萎,Mock 框架允許你在運(yùn)行時創(chuàng)建 Mock 對象并且定義它的行為亡电。
一個典型的例子是把 Mock 對象模擬成數(shù)據(jù)的提供者。在正式的生產(chǎn)環(huán)境中它會被實(shí)現(xiàn)用來連接數(shù)據(jù)源硅瞧。但是我們在測試的時候 Mock 對象將會模擬成數(shù)據(jù)提供者來確保我們的測試環(huán)境始終是相同的份乒。
Mock 對象可以被提供來進(jìn)行測試。因此腕唧,我們測試的類應(yīng)該避免任何外部數(shù)據(jù)的強(qiáng)依賴或辖。
通過 Mock 對象或者 Mock 框架,我們可以測試代碼中期望的行為枣接。譬如說颂暇,驗(yàn)證只有某個存在 Mock 對象的方法是否被調(diào)用了。
2.4. 使用 Mockito 生成 Mock 對象
Mockito 是一個流行 mock 框架月腋,可以和JUnit結(jié)合起來使用蟀架。Mockito 允許你創(chuàng)建和配置 mock 對象。使用Mockito可以明顯的簡化對外部依賴的測試類的開發(fā)榆骚。
一般使用 Mockito 需要執(zhí)行下面三步
模擬并替換測試代碼中外部依賴片拍。
執(zhí)行測試代碼
驗(yàn)證測試代碼是否被正確的執(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òu)建工具碉钠。
3.4. 以 OSGi 或者 Eclipse 插件形式添加 Mockito 依賴
在 Eclipse RCP 應(yīng)用依賴通掣倩海可以在 p2 update 上得到卷拘。Orbit 是一個很好的第三方倉庫,我們可以在里面尋找能在 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)建對象,譬如直接調(diào)用 mock() 方法工闺。
4.2. 使用 Mockito 創(chuàng)建和配置 mock 對象
除了上面所說的使用 mock() 靜態(tài)方法外乍赫,Mockito 還支持通過 @Mock
注解的方式來創(chuàng)建 mock 對象。
如果你使用注解陆蟆,那么必須要實(shí)例化 mock 對象雷厂。Mockito 在遇到使用注解的字段的時候,會調(diào)用MockitoAnnotations.initMocks(this)
來初始化該 mock 對象叠殷。另外也可以通過使用@RunWith(MockitoJUnitRunner.class)
來達(dá)到相同的效果改鲫。
通過下面的例子我們可以了解到使用@Mock
的方法和MockitoRule
規(guī)則。
import static org.mockito.Mockito.*;
public class MockitoTest {
@Mock
MyDatabase databaseMock; (1)
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); (2)
@Test
public 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 對象
使用已經(jīng)創(chuàng)建的mock初始化這個類
在測試環(huán)境下林束,執(zhí)行測試類中的代碼
使用斷言確保調(diào)用的方法返回值為 true
驗(yàn)證 query 方法是否被
MyDatabase
的 mock 對象調(diào)用
4.3. 配置 mock
當(dāng)我們需要配置某個方法的返回值的時候像棘,Mockito 提供了鏈?zhǔn)降?API 供我們方便的調(diào)用
when(…?.).thenReturn(…?.)
可以被用來定義當(dāng)條件滿足時函數(shù)的返回值,如果你需要定義多個返回值诊县,可以多次定義讲弄。當(dāng)你多次調(diào)用函數(shù)的時候,Mockito 會根據(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)建 mock
MyClass test = Mockito.mock(MyClass.class);
// 自定義 getUniqueId() 的返回值
when(test.getUniqueId()).thenReturn(43);
// 在測試中使用mock對象
assertEquals(test.getUniqueId(), 43);
}
// 返回多個值
@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)));
}
對于無返回值的函數(shù)瓶摆,我們可以使用doReturn(…?).when(…?).methodCall
來獲得類似的效果。例如我們想在調(diào)用某些無返回值函數(shù)的時候拋出異常性宏,那么可以使用doThrow
方法群井。如下面代碼片段所示
import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
// 下面測試用例描述了如何使用doThrow()方法
@Test(expected=IOException.class)
public void testForIOException() {
// 創(chuàng)建并配置 mock 對象
OutputStream mockStream = mock(OutputStream.class);
doThrow(new IOException()).when(mockStream).close();
// 使用 mock
OutputStreamWriter streamWriter= new OutputStreamWriter(mockStream);
streamWriter.close();
}
4.4. 驗(yàn)證 mock 對象方法是否被調(diào)用
Mockito 會跟蹤 mock 對象里面所有的方法和變量。所以我們可以用來驗(yàn)證函數(shù)在傳入特定參數(shù)的時候是否被調(diào)用毫胜。這種方式的測試稱行為測試书斜,行為測試并不會檢查函數(shù)的返回值,而是檢查在傳入正確參數(shù)時候函數(shù)是否被調(diào)用酵使。
import static org.mockito.Mockito.*;
@Test
public void testVerify() {
// 創(chuàng)建并配置 mock 對象
MyClass test = Mockito.mock(MyClass.class);
when(test.getUniqueId()).thenReturn(43);
// 調(diào)用mock對象里面的方法并傳入?yún)?shù)為12
test.testing(12);
test.getUniqueId();
test.getUniqueId();
// 查看在傳入?yún)?shù)為12的時候方法是否被調(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 對象
@Spy或者spy()
方法可以被用來封裝 java 對象荐吉。被封裝后,除非特殊聲明(打樁 stub)口渔,否則都會真正的調(diào)用對象里面的每一個方法
import static org.mockito.Mockito.*;
// Lets mock a LinkedList
List list = new LinkedList();
List spy = spy(list);
// 可用 doReturn() 來打樁
doReturn("foo").when(spy).get(0);
// 下面代碼不生效
// 真正的方法會被調(diào)用
// 將會拋出 IndexOutOfBoundsException 的異常样屠,因?yàn)?List 為空
when(spy.get(0)).thenReturn("foo");
方法verifyNoMoreInteractions()
允許你檢查沒有其他的方法被調(diào)用了。
4.6. 使用 @InjectMocks 在 Mockito 中進(jìn)行依賴注入
我們也可以使用@InjectMocks
注解來創(chuàng)建對象,它會根據(jù)類型來注入對象里面的成員方法和變量痪欲。假定我們有 ArticleManager 類
public class ArticleManager {
private User user;
private ArticleDatabase database;
ArticleManager(User user) {
this.user = user;
}
void setDatabase(ArticleDatabase database) { }
}
這個類會被 Mockito 構(gòu)造悦穿,而類的成員方法和變量都會被 mock 對象所代替,正如下面的代碼片段所示:
@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 有一個叫 initialize() 的方法被調(diào)用了
// 使用 ArticleListener 來調(diào)用 addListener 方法
manager.initialize();
// 驗(yàn)證 addListener 方法被調(diào)用
verify(database).addListener(any(ArticleListener.class));
}
}
- 創(chuàng)建ArticleManager實(shí)例并注入Mock對象
更多的詳情可以查看
http://docs.mockito.googlecode.com/hg/1.9.5/org/mockito/InjectMocks.html.
4.7. 捕捉參數(shù)
ArgumentCaptor
類允許我們在verification期間訪問方法的參數(shù)业踢。得到方法的參數(shù)后我們可以使用它進(jìn)行測試栗柒。
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();
@Captor
private ArgumentCaptor> captor;
@Test
public 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ù)類型則不能夠被測試
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寫一個Instrumented Unit Test
6.1. 創(chuàng)建一個測試的Android 應(yīng)用
創(chuàng)建一個包名為com.vogella.android.testing.mockito.contextmock
的Android應(yīng)用负蠕,添加一個靜態(tài)方法
,方法里面創(chuàng)建一個包含參數(shù)的Intent倦畅,如下代碼所示:
public static Intent createQuery(Context context, String query, String value) {
// 簡單起見遮糖,重用MainActivity
Intent 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 JVM
androidTestCompile '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 JVM
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.+'
}
6.3. 創(chuàng)建測試
使用 Mockito 創(chuàng)建一個單元測試來驗(yàn)證在傳遞正確 extra data 的情況下,intent 是否被觸發(fā)叠赐。
因此我們需要使用 Mockito 來 mock 一個Context
對象欲账,如下代碼所示:
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 {
@Test
public 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)建一個 mock 對象
7.1. 目標(biāo)
創(chuàng)建一個 Api,它可以被 Mockito 來模擬并做一些工作
7.2. 創(chuàng)建一個Twitter API 的例子
實(shí)現(xiàn) TwitterClient
類芭概,它內(nèi)部使用到了 ITweet
的實(shí)現(xiàn)赛不。但是ITweet
實(shí)例很難得到,譬如說他需要啟動一個很復(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í)例
為了能夠不啟動復(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()
方法的時候?qū)蛴?"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)行測試桩蓉,查看代碼是否測試通過淋纲。
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 hostname
hostname = 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 {
// 測試代碼
@Test
public void testSomething() {
mockStatic( NetworkUtil.class );
when( NetworkReader.getLocalHostname() ).andReturn( "localhost" );
// 與 NetworkReader 協(xié)作的測試
}
8.2.用封裝的方法代替Powermock
有時候我們可以在靜態(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/ 高級android教程(竟然是個妹子)