深入淺出Android單元測試(三)詳解Mockito

歡迎關注程序引力

對Android有依賴的單元測試如何寫栖袋?怎樣脫離真機與模擬器叹卷?本文將會對Java測試框架mockito做詳細介紹舒岸。

若有錯漏绅作,煩請斧正。轉(zhuǎn)載請注明出處蛾派。

前言

在單元測試基礎篇中俄认,介紹了單元測試的基礎以及JUnit以及AndroidJUnintRunner,對于測試不依賴于Android的Java代碼碍脏,可以僅使用JUnit進行測試梭依。但若需要測試的代碼依賴于Android,則需要結(jié)合AndroidJUnitRunner并運行在真機或模擬器上才行典尾。

不知開發(fā)者有沒有一個疑問役拴?能否有一種方式可以讓對Android存在依賴的測試代碼也直接運行于本地的JVM中?答案是肯定的钾埂。mockito則提供了這樣的能力河闰,能夠脫離真機與模擬器運行對安卓有依賴的測試代碼。

Mockito概述

在實際的工程中褥紫,待測試的代碼往往不是孤立存在的姜性,其中的方法或變量可能依賴外部的變量。為此髓考,為了測試某一部分代碼部念,需要將其他被依賴的代碼關聯(lián)進來。更為可能的情況是氨菇,這些被依賴的代碼可能還存在更多的依賴儡炼,這就導致開發(fā)者為了測試某一小部分的代碼,而不得不與龐大的其他代碼相關聯(lián)查蓉。為了解決這個問題乌询,Mockito應運而生。

Mockito是一款Java測試框架豌研,它通過構(gòu)造一些‘假’的對象妹田,來將測試代碼與其依賴隔離唬党,進而提高了測試的易用性與運行效率。

Mockito主要有兩個作用:

  • 驗證某個對象的行為鬼佣。
  • 驗證某個對象的行為的調(diào)用次數(shù)驶拱。

Mockito優(yōu)點:

  • 能夠?qū)⒋郎y試代碼與其依賴進行隔離
  • 讓一些對Android存在一定依賴的測試代碼,運行在本地JVM上

適合使用Mockito的場景:

  • 待測試代碼對Android有較小的依賴
  • 開發(fā)者希望待測試代碼與其依賴隔離開

引入Mockito

使用Mockito前需要引入相應的庫沮趣,除了JUnit框架外屯烦,還需在模塊目錄(如APP)的build.gradle中,添加如下依賴:

dependencies {
    testImplementation 'junit:junit:4.12'
    testImplementation 'org.mockito:mockito-core:1.10.19'
    androidTestImplementation 'org.mockito:mockito-core:1.10.19'
}

同步后即可引入Mockito房铭。

Mockito的基本對象

Mockito提供了兩種基本對象,分別是mock對象與spy對象温眉。

  • mock對象:完全虛構(gòu)的對象缸匪,除了自定義的行為外,無其他行為类溢。
  • spy對象:部分虛構(gòu)的對象凌蔬,除了自定義行為外,其他行為參考真實對象的行為闯冷。

可以這么理解砂心,spy對象是在真實對象的基礎上,自定義了部分行為蛇耀,其他行就是原本真實對象的行為辩诞。而mock對象是完全虛構(gòu),完全自定義的纺涤,對于沒有定位的行為译暂,就僅有默認值。

撰寫測試代碼總體步驟

撰寫基于Mockito框架的測試代碼撩炊,主要步驟為:

  • 構(gòu)造mock/spy對象
  • 定義對象的行為
  • 運行測試代碼
  • 校驗測試代碼運行結(jié)果與預期結(jié)果

Mock對象簡單用法

構(gòu)造mock對象

構(gòu)造mock對象的方式有兩類外永,一類是通過mock方法進行創(chuàng)建,另一類是通過@Mock注解的方式創(chuàng)建拧咳。對于后者伯顶,又分為三種不同的方式

通過mock方法創(chuàng)建對象:

@Before
public void setUp() throws Exception {
    mockedList = mock(ArrayList.class);
}

對于setUp()方法,由于添加了@Before注解骆膝,故該方法在所有@Test方法之前執(zhí)行祭衩。在該方法中,將需要被構(gòu)造的類傳入mock方法中谭网,能夠創(chuàng)建出mock對象汪厨。

通過@Mock注解的方式創(chuàng)建對象

  • 方法1:使用前調(diào)用iniMocks方法:
@Mock
private ArrayList mockedList;

@Before
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
}
  • 方法2:通過@RunWith注解:
@RunWith(MockitoJUnitRunner.class)
public class MockitoJUnitRunnerTest {
    @Mock
    AccountData accountData;
}
  • 方法3:通過MockitoRule
public class MockitoRuleTest {
    @Mock
    AccountData accountData;
    @Rule
    public MockitoRule mockitoRule = MockitoJUnit.rule();
}

不管通過何種方式,創(chuàng)建出mock對象愉择,是執(zhí)行后續(xù)測試流程的前提劫乱。

定義mock對象行為

when(mockedList.get(0)).thenReturn("first");

定義mock對象行為的語法非常通俗易懂织中,其語法為當(when)調(diào)用什么時,返回(thenReturn)什么衷戈。對于上面的例子狭吼,即當調(diào)用mockedList.get(0)時,返回“first".

運行測試代碼

構(gòu)造mock對象殖妇,并且定義了該對象的行為后刁笙,即可運行該對象的相應方法與其他需要被測試的方法。在該簡單的例子中谦趣,該步驟的代碼為:

String res = mockedList.get(0);

校驗測試代碼運行結(jié)果與預期結(jié)果

得到運行結(jié)果后疲吸,可以通過斷言與預期結(jié)構(gòu)比較,得到測試結(jié)果前鹅。

assertEquals(res,"first")

本例完整代碼

@RunWith(MockitoJUnitRunner.class)
public class MockitoTest {
    @Mock
    ArrayList mockedList;    //創(chuàng)建mock對象
    
    @Before
    public void setUp(){
        when(mockedList.get(0)).thenReturn("first");    //定義mock行為
    }
    
    @Test
    public void testMockito(){
        String res = (String)mockedList.get(0);    //調(diào)用mock對象方法
        assertEquals(res, "first");    //比較實際結(jié)果與預期
    }
}

Mock對象存在安卓依賴的用法

對于存在Android依賴的情況摘悴,其測試代碼的基本思路也與上文中的例子是一致的。也是在創(chuàng)建對象后舰绘,定義對象行為蹂喻,然后調(diào)用對象方法并與預期比較。在下面的例子中捂寿,就不分布介紹了口四,具體看代碼右側(cè)的注釋。

以安卓官網(wǎng)一個的例子看:

@RunWith(MockitoJUnitRunner.class)    //聲明使用Mockito
public class UnitTestSample {
    private static final String FAKE_STRING = "HELLO WORLD";

    @Mock
    Context mMockContext;     //mock構(gòu)造一個context對象

    @Test
    public void readStringFromContext_LocalizedString() {
        // 定義該mock對象的行為秦陋,即通過id或者字符串
        when(mMockContext.getString(R.string.hello_world)).thenReturn(FAKE_STRING);
        
        // 將該mockContext傳入某個類蔓彩,安卓場景中一般都會傳入context
        ClassUnderTest myObjectUnderTest = new ClassUnderTest(mMockContext);

        // 運行測試代碼,獲取字符串
        String result = myObjectUnderTest.getHelloWorldString();

        // 校驗實際結(jié)果與預期結(jié)果是否一致
        assertThat(result, is(FAKE_STRING));
    }
}

Verify語句的用法

在前面的例子中踱侣,主要是定義mock對象的行為后粪小,驗證其行為與預期是否一致。但存在著另外一種情況抡句,開發(fā)者希望驗證mock對象的某個方法是否調(diào)用探膊,以及調(diào)用的其他情況。那么可以使用Mockito提供的verify方法待榔。

verify語句使用的形式為:

verify(mock對象).方法(參數(shù))

如果該mock對象的該方法被調(diào)用過(調(diào)用時傳入的參數(shù)也一樣)逞壁,則該測試通過。

verify的簡單例子

public class MockitoTest {
    @Mock
    ArrayList mockedList;

    @Before
    public void setUp(){
        //when(mockedList.get(0)).thenReturn("first");    //即使注釋掉定義定位的代碼
    }

    @Test
    public void testMockito(){
        String res = (String)mockedList.get(0);    //調(diào)用mock對象的get方法
        verify(mockedList).get(0);    //驗證通過
        verify(mockedList).get(1);    //驗證不通過
    }
}

在這個例子中锐锣,盡管沒有定義mock對象的行為腌闯,但只要調(diào)用了該方法(此時get返回null),調(diào)用verify方法也能使驗證通過雕憔。若參數(shù)不一致姿骏,則驗證不通過。

verify方法的其他用法

除了簡單驗證mock對象的某個方法是否調(diào)用斤彼,verify還可以驗證該方法的調(diào)用情況分瘦。

 verify(mockedList).get(0);    //驗證方法是否調(diào)用蘸泻,且參數(shù)傳入的是0
 verify(mockedList, times(1)).get(0);    //驗證方法是否調(diào)用且只調(diào)用了1次
 verify(mockedList, never()).get(0);    //驗證方法是否沒有被調(diào)用過
 verify(mockedList, atLeast(2)).get(0);    //驗證方法是否調(diào)用且調(diào)用2次以上
 verify(mockedList, atMost(5)).get(0);    //驗證方法是否調(diào)用且最多調(diào)用5次
 verify(mockedList).get(anyInt());    //驗證方法是否調(diào)用,且傳入的參數(shù)是任意整型數(shù)

spy對象的用法

spy對象與mock對象的區(qū)別在于:

  • mock對象:通過類進行創(chuàng)建嘲玫,是完全虛構(gòu)的對象悦施,除了自定義的行為外,無其他行為去团。
  • spy對象:通過對象進行創(chuàng)建抡诞,是部分虛構(gòu)的對象,除了自定義行為外土陪,其他行為參考真實對象的行為昼汗。

也就是說,對于spy對象旺坠,除了自定義的行為外乔遮,均把它當做原本的那個對象處理。簡單的例子如下:

@RunWith(MockitoJUnitRunner.class)
public class AssertEquals {
    @Test
    public void testMockito(){
        ArrayList<Integer> integers = new ArrayList<>();
        ArrayList<Integer> spyList = spy(integers);    //通過真實的對象創(chuàng)建spy對象

        spyList.add(1);    //真實對象添加了一個元素
        System.out.println(spyList.get(0));    //查看其元素值取刃,返回1

        when(spyList.size()).thenReturn(100);    //定義其行為,虛構(gòu)其size為100
        System.out.println(spyList.size());    //查看其size,返回100

        when(spyList.get(0)).thenReturn(2);    //定義其行為出刷,虛構(gòu)其第一個元素值
        System.out.println(spyList.get(0));    //返回2璧疗,即虛構(gòu)的行為會覆蓋原有真實的行為
    }
}

從上面的例子中可知,spy僅僅是對真實對象的行為進行部分虛構(gòu)馁龟,虛構(gòu)的部分可以覆蓋原來的部分崩侠。

總結(jié)

Android開發(fā)者應該了解到,在撰寫單元測試之前坷檩,需要考慮自己的測試場景是什么却音,依賴是什么,需求是什么矢炼。如果需要測試的代碼對Android有一定的依賴系瓢,同時又希望這樣的單元測試是運行在本地JVM中,則可以選擇mockito框架進行測試句灌。

Mockito框架的使用方法也很簡單夷陋,主要分為四步:

  • 構(gòu)造mock/spy對象
  • 定義mock對象的行為
  • 運行測試代碼
  • 校驗測試代碼運行結(jié)果與預期結(jié)果

只要記住這四步,同時查閱Mockito的API文檔胰锌,了解它支持定義哪些行為骗绕,即可寫出符合實際需要的單元測試用例。

若你喜歡本文或覺得有所幫助资昧,請點贊或關注酬土。
你的支持是對筆者最大的鼓勵與肯定。比芯~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末格带,一起剝皮案震驚了整個濱河市撤缴,隨后出現(xiàn)的幾起案子刹枉,更是在濱河造成了極大的恐慌,老刑警劉巖腹泌,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘶卧,死亡現(xiàn)場離奇詭異,居然都是意外死亡凉袱,警方通過查閱死者的電腦和手機芥吟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來专甩,“玉大人钟鸵,你說我怎么就攤上這事〉佣悖” “怎么了棺耍?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長种樱。 經(jīng)常有香客問我蒙袍,道長,這世上最難降的妖魔是什么嫩挤? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任害幅,我火速辦了婚禮,結(jié)果婚禮上岂昭,老公的妹妹穿的比我還像新娘以现。我一直安慰自己,他們只是感情好约啊,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布邑遏。 她就那樣靜靜地躺著,像睡著了一般恰矩。 火紅的嫁衣襯著肌膚如雪记盒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天枢里,我揣著相機與錄音孽鸡,去河邊找鬼。 笑死彬碱,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的奥洼。 我是一名探鬼主播巷疼,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嚼沿,長吁一口氣:“原來是場噩夢啊……” “哼骡尽!你這毒婦竟也來了遣妥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤攀细,失蹤者是張志新(化名)和其女友劉穎箫踩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谭贪,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡境钟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了俭识。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片慨削。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖套媚,靈堂內(nèi)的尸體忽然破棺而出缚态,到底是詐尸還是另有隱情,我是刑警寧澤堤瘤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布猿规,位于F島的核電站,受9級特大地震影響宙橱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蘸拔,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一师郑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧调窍,春花似錦宝冕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缔恳,卻和暖如春宝剖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背歉甚。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工万细, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纸泄。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓赖钞,卻偏偏與公主長得像腰素,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子雪营,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,133評論 25 707
  • 用兩張圖告訴你弓千,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 12,724評論 2 59
  • 1. 預備知識 如果需要往下學習献起,你需要先理解 Junit 框架中的單元測試洋访。 如果你不熟悉 JUnit,請查看下...
    會飛的大象_閱讀 2,697評論 0 4
  • 什么是單元測試 在計算機編程中征唬,單元測試(Unit Testing)又稱為模塊測試, 是針對程序模塊(軟件設計的最...
    HelloCsl閱讀 10,955評論 1 46
  • 語文:基礎知識手冊每十天重點掌握捌显,作文素材每天精讀5篇。 數(shù)學:將前面的選擇題总寒、填空題加上后面的四道大題一起重做一...
    九歌寒閱讀 182評論 0 0