成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

Android單元測(cè)試 - 如何開(kāi)始?

Developer / 965人閱讀

摘要:寫(xiě)單元測(cè)試時(shí),應(yīng)該把這些依賴(lài)隔離,讓每個(gè)單元保持獨(dú)立。以上的各種原因,都會(huì)影響單元測(cè)試的結(jié)果。在單元測(cè)試的基礎(chǔ)上,將相關(guān)模塊組合成為子系統(tǒng)或系統(tǒng)進(jìn)行測(cè)試,稱(chēng)為集成測(cè)試??梢钥吹?,單元測(cè)試速度比集成測(cè)試,也叫測(cè)試要快,并且開(kāi)發(fā)成本也是最低。

原文鏈接:http://www.jianshu.com/p/bc99678b1d6e
作者:鍵盤(pán)男kkmike999

回顧

《談?wù)劄槭裁磳?xiě)單元測(cè)試》

基本單元測(cè)試框架

Java單元測(cè)試框架:Junit、Mockito、Powermockito等;Android:Robolectric、AndroidJUnitRunner、Espresso等。

最開(kāi)始建議先學(xué)習(xí)Junit & Mockito。這兩款框架是java領(lǐng)域應(yīng)用非常普及,使用簡(jiǎn)單,網(wǎng)上文章非常多,官網(wǎng)的說(shuō)明也很清晰。junit運(yùn)行在jvm上,所以只能測(cè)試純java,若要測(cè)試依賴(lài)android庫(kù)的代碼,可以用mockito隔離依賴(lài)(下面會(huì)談及)。

Junit官網(wǎng)
Mockito官網(wǎng)

之后學(xué)習(xí)AndroidJUnitRunner,Google官方的android單元測(cè)試框架之一,使用跟Junit是一樣的,只不過(guò)需要運(yùn)行在android真機(jī)或模擬器環(huán)境。由于mockito只在jvm環(huán)境生效,而android是運(yùn)行在DalvikART,所以AndroidJUnitRunner不能使用mockito。

然后可以嘗試Robolectric & Espresso。Robolectric運(yùn)行在jvm上,但是框架本身引入了android依賴(lài)庫(kù),所以可以做android單元測(cè)試,運(yùn)行速度比運(yùn)行在真機(jī)or模擬器快。但Robolectric也有局限性,例如不支持加載so,測(cè)試代碼也有點(diǎn)別扭。當(dāng)然,robolectric可以配合junit、mockito使用。Espresso也是Google官方的android單元測(cè)試框架之一,強(qiáng)大就不用說(shuō)了,測(cè)試代碼非常簡(jiǎn)潔。Espresso本身運(yùn)行在真機(jī)上,因此android任何代碼都能運(yùn)行,不像junit&mockito那樣隔離依賴(lài)。缺點(diǎn)也是顯而易見(jiàn),由于運(yùn)行在真機(jī),不能避免“慢”。

Robolectric官網(wǎng)
Android-testing-support-library官網(wǎng)

其實(shí)espresso應(yīng)該是幾款框架中最簡(jiǎn)單的,但筆者還是建議先學(xué)習(xí)junit&mockito。因?yàn)樾率趾芸赡軙?huì)因?yàn)閑spresso的強(qiáng)大、簡(jiǎn)單,而忽略了junit做單元測(cè)試帶來(lái)的巨大意義。例如,前文提到“快速定位bug”、“提高代碼質(zhì)量”,espresso慢,有違“快速”;用espresso不用修改工程任何代碼,這不利于提高代碼質(zhì)量。

本文主要介紹junitmockito,以及單元測(cè)試一些重要概念。

Junit

先給大家上兩段代碼壓壓驚:

public class Calculater {
    public int add(int a, int b) {
        return a + b;
    }
}
AssertEquals

單元測(cè)試用例:

public class CalculaterTest {

    Calculater calculater = new Calculater();

    @org.junit.Test
    public void testAdd() {
        int a = 1;
        int b = 2;

        int result = calculater.add(a, b);

        Assert.assertEquals(result, 3); // 驗(yàn)證result==3,如果不正確,測(cè)試不通過(guò)
    }
}

以上是一個(gè)要測(cè)試的類(lèi)Calculater和測(cè)試用例CalculaterTest。在IntellijAndroid Studio對(duì)類(lèi)右鍵->run CalculaterTest,用例中所有被@org.junit.Test注解的方法,就會(huì)被執(zhí)行。

測(cè)試通過(guò)。

如果代碼改成Assert.assertEquals(result, 4);,測(cè)試會(huì)失敗。

Verify

verify的作用,是驗(yàn)證函數(shù)是否被調(diào)用(以及調(diào)用了多少次)。

public class CalculaterTest {

    @org.junit.Test
    public void testAdd2() {
        calculater = mock(Calculater.class);
        
        calculater.add(1, 2);

        verify(calculater).add(1, 2); // 驗(yàn)證calculater.add(a, b)是否被調(diào)用過(guò),且a==1 && b==2
        // 測(cè)試通過(guò)
    }
}

是不是很簡(jiǎn)單?

Mockito

官網(wǎng)這樣描述:

Mockito is a mocking framework that tastes really good. It lets you write beautiful tests with a clean & simple API.

大概意思是,Mockito是一個(gè)體驗(yàn)很好的mocking框架,它可以讓你寫(xiě)出漂亮、簡(jiǎn)潔的測(cè)試代碼。

什么是mocking?下文會(huì)詳細(xì)說(shuō)明。不如先讓你感受一下mockito代碼:

public interface IMathUtils {
    public int abs(int num); // 求絕對(duì)值
}
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class MockTest {

    public static void main(String[] args) {
        IMathUtils mathUtils = mock(IMathUtils.class); // 生成mock對(duì)象

        when(mathUtils.abs(-1)).thenReturn(1); // 當(dāng)調(diào)用abs(-1)時(shí),返回1

        int abs = mathUtils.abs(-1); // 輸出結(jié)果 1
        
        Assert.assertEquals(abs, 1);// 測(cè)試通過(guò)
    }
}

可以發(fā)現(xiàn)IMathUtils是一個(gè)接口,根本就沒(méi)有實(shí)現(xiàn),用Mockito框架mock之后,IMathUtils.abs(-1)就有返回值1了。這就是Mockito神奇的地方!Mockito代理了IMathUtils.abs(num)的行為,只要調(diào)用時(shí)符合指定參數(shù)(代碼中指定參數(shù)-1),就可以得到映射的返回值

Mockito的語(yǔ)法when...thenReturn...相當(dāng)直觀,只要你小學(xué)有學(xué)英語(yǔ)^_^都能看懂。

讀者肯定認(rèn)為Mockito用了Java代理,實(shí)際上要更高級(jí)一點(diǎn),Mockito底層用了CGLib(github/cglib)做動(dòng)態(tài)代理。

依賴(lài)隔離

依賴(lài)隔離,這是單元測(cè)試中一個(gè)非常重要的概念。一個(gè)單元的代碼,通常會(huì)有各種依賴(lài)。寫(xiě)單元測(cè)試時(shí),應(yīng)該把這些依賴(lài)隔離,讓每個(gè)單元保持獨(dú)立。舉個(gè)例子:

public class Calculater {

    public double divide(int a, int b) {
        // 檢測(cè)被除數(shù)是否為0
        if (MathUtils.checkZero(b)) {
            throw new RuntimeException("dividend is zero");
        }

        return (double) a / b;
    }
}
public class MathUtils {
    public static boolean checkZero(int num) {
        return num == 0;
    }
}

divide(a,b)計(jì)算a除以b,但被除數(shù)b不應(yīng)該為0,所以用MathUtils.checkZero(b)驗(yàn)證b==0。咋看這里好像沒(méi)什么問(wèn)題,但是,如果MathUtils.checkZero里面的判斷邏輯寫(xiě)錯(cuò)呢?例如:

 public static boolean checkZero(int num) {
    return  num != 0; // bug
 }

如果不是num==0那么簡(jiǎn)單,而是更復(fù)雜的算法呢?

因?yàn)?b>Calculater引用的任何依賴(lài),都可能出錯(cuò)。更糟糕的是,如果用junit做單元測(cè)試,依賴(lài)?yán)锩婵赡苁?strong>Android庫(kù)或者jni native方法,依賴(lài)方法一執(zhí)行就會(huì)報(bào)錯(cuò)。以上的各種原因,都會(huì)影響單元測(cè)試的結(jié)果。所以,我們對(duì)代碼做如下改進(jìn):

public class Calculater {

    IMathUtils mathUtils;
    
    public double divide(int a, int b) {
        if (mathUtils.checkZero(b)) {
            throw ...
        }
        return (double) a / b;
    }
}
public interface IMathUtils {
    public boolean checkZero(int num);
}

我們可以在Calculater構(gòu)造方法傳入IMathUtils派生類(lèi),又或者用setter。在項(xiàng)目執(zhí)行代碼中,傳MathUtils,而單元測(cè)試時(shí),可以寫(xiě)一個(gè)MathUtilsTest繼承IMathUtils,傳給Calculater。只要保證MathUtilsTest.checkZero()正確就行。經(jīng)過(guò)這么重構(gòu),Calculater 就不依賴(lài)原來(lái)的MathUtils,單元測(cè)試時(shí)可以替換專(zhuān)門(mén)的實(shí)現(xiàn),達(dá)到了依賴(lài)隔離的目的。

有同學(xué)會(huì)問(wèn),這樣豈不是每個(gè)依賴(lài)都要寫(xiě)一個(gè)專(zhuān)門(mén)給單元測(cè)試的類(lèi)嗎?這就等于拷貝多一份代碼,并且寫(xiě)各種接口,而且不能保證單元測(cè)試的類(lèi)一定正確。

說(shuō)得很有道理。筆者為了盡量簡(jiǎn)單地演示代碼,舉了一個(gè)非常簡(jiǎn)單的例子。我們?nèi)绾巫寙卧獪y(cè)試更簡(jiǎn)潔,并且讓它閱讀起來(lái)更有意義呢?

Mock

為了更好地解決上述問(wèn)題,我們引入Mock概念。Mock,翻譯為模擬,在單元測(cè)試mock可以模擬返回?cái)?shù)據(jù),也可以模擬接口、類(lèi)的行為

什么是模擬行為?例如剛才mathUtils.checkZero(b),意義為:“當(dāng)mathUtils調(diào)用checkZero(num)”時(shí),判斷 num==0;又或者:“當(dāng)調(diào)用checkZero(0)時(shí)返回true,num為其他值時(shí)返回false”,返回的true、false就是模擬數(shù)據(jù)。

例如,需要測(cè)試a=2,b=1a=2,b=0調(diào)用divide(a,b)兩者結(jié)果分別是2,拋出錯(cuò)誤,使用mockito框架mock mathUtils.checkZero()的行為,代碼如下:

public static void main(String[] args) {
    // 生成IMathUtils模擬對(duì)象
    IMathUtils mathUtils = mock(IMathUtils.class);

    when(mathUtils.checkZero(1)).thenReturn(false); // 當(dāng)num==1時(shí),checkZero(num)返回false
    when(mathUtils.checkZero(0)).thenReturn(true); // 當(dāng)num==0時(shí),checkZero(num)返回true

    Calculater calculater = new Calculater(mathUtils);

    assertEquals(calculater.divide(2,1), 2); // 驗(yàn)證 divide(2,1) 結(jié)果是2

    try {
        calculater.divide(2, 0); // 預(yù)期拋出錯(cuò)誤
        throw new RuntimeException("no expectant exception"); // 如果divide沒(méi)拋錯(cuò),則此處拋錯(cuò)
    } catch (Exception e) {
        Assert.assertEquals(e.getMessage(), "dividend is zero"); // 驗(yàn)證錯(cuò)誤信息
    }
}

這段測(cè)試代碼可以運(yùn)行通過(guò)!

代碼剖析:

mock(IMathUtils.class)生成IMathUtils類(lèi)的模擬對(duì)象(稱(chēng)mock對(duì)象)。這個(gè)mock對(duì)象調(diào)用任何方法都不會(huì)被實(shí)際執(zhí)行;

when(mathUtils.checkZero(1)).thenReturn(false),當(dāng)調(diào)用checkZero(num)并且num==1,返回false,這里mockito模擬了checkZero()行為,并模擬了返回?cái)?shù)據(jù);

所以,calculater.divide(2,1)返回結(jié)果2,calculater.divide(2, 0)拋出RuntimeException。

以上例子描述了,使用mockito模擬類(lèi)方法和返回?cái)?shù)據(jù),通過(guò)mock隔離了Calculater對(duì)IMathUtils實(shí)現(xiàn)類(lèi)的依賴(lài),并通過(guò)單元測(cè)試,驗(yàn)證了divide()的邏輯正確性。

條件覆蓋 無(wú)限條件

要驗(yàn)證程序正確性,必然要給出所有可能的條件(極限編程),并驗(yàn)證其行為或結(jié)果,才算是100%覆蓋條件。實(shí)際項(xiàng)目中,驗(yàn)證邊界條件和一般條件就OK了。

還是上面那個(gè)例子,只給出兩個(gè)條件:a=2,b=1a=2,b=0a=2,b=1是一般條件,b=0是邊界條件,還有一些邊界條件a=NaN,b=NaN等。要驗(yàn)證除法正確性,恐怕得給出無(wú)限的條件,實(shí)際上,只要驗(yàn)證幾個(gè)邊界條件和一般條件,基本認(rèn)為代碼是正確了。

有限條件

再舉個(gè)例子:stateA="a0"、"a1", stateB="b0"、"b1"、"b2",根據(jù)stateA、stateB不同組合輸出不同結(jié)果,例如a0b0輸出a0b0,a0b1輸出a0b1,所以,共2*3=6種情況。這時(shí),并不存在邊界條件,所以條件都是特定條件,并且條件有限。

這種情況在項(xiàng)目中很常見(jiàn),以筆者經(jīng)驗(yàn),建議單元測(cè)試時(shí)把所有情況都驗(yàn)證一遍,確保沒(méi)有遺漏。

單元測(cè)試不是集成測(cè)試 集成測(cè)試

集成測(cè)試,也叫組裝測(cè)試、聯(lián)合測(cè)試。在單元測(cè)試的基礎(chǔ)上,將相關(guān)模塊組合成為子系統(tǒng)或系統(tǒng)進(jìn)行測(cè)試,稱(chēng)為集成測(cè)試。通俗一點(diǎn),集成測(cè)試就是把多個(gè)(最少2個(gè))組件合在一起,測(cè)試某個(gè)功能片段,甚至是多帶帶功能。

單元測(cè)試僅針對(duì)單元

在微信群很多同學(xué)問(wèn):“用Robolectric能不能請(qǐng)求網(wǎng)絡(luò)”,"Junit能直接請(qǐng)求服務(wù)器嗎"?

例如,我們使用MVP模式,如果我們想測(cè)試:調(diào)用PresenterA接口,請(qǐng)求真實(shí)網(wǎng)絡(luò),并且返回?cái)?shù)據(jù)后,解析成對(duì)象,并且根據(jù)返回?cái)?shù)據(jù)執(zhí)行對(duì)應(yīng)邏輯。這明顯就是一個(gè)集成測(cè)試,而不是單元測(cè)試。PresenterA是一個(gè)單元,M層的Repository、DAO等是一個(gè)單元,更底層的sqlite第三方庫(kù)、網(wǎng)絡(luò)請(qǐng)求第三方庫(kù)(okhttp等) 也是單元.....組合了n個(gè)單元的測(cè)試,是集成測(cè)試

Robolectric、Junit能否請(qǐng)求網(wǎng)絡(luò)?

包括筆者在內(nèi),很多同學(xué)一開(kāi)始都會(huì)有這個(gè)疑問(wèn)。

閱讀了本文第一部分,應(yīng)該了解到robolectric、junit是運(yùn)行在jvm,只要有一點(diǎn)點(diǎn)java開(kāi)發(fā)經(jīng)驗(yàn)的同學(xué),都知道jvm本身能連接網(wǎng)絡(luò)。如果你調(diào)用的方法所依賴(lài)的一切代碼,都不依賴(lài)Android庫(kù)(例如okhttp、retrofit),那99%都能在jvm上跑,并且能請(qǐng)求服務(wù)器。如果不幸有Android依賴(lài),很大概率還是能在robolectric上跑的。

為什么robolectric不是100%能跑通測(cè)試呢?Robolectric僅支持API21及以下,并且不支持jni庫(kù)。因此,如果你的代碼依賴(lài)了API21以上接口或者jni接口,robolectric也無(wú)能為力。天?。≡趺崔k?

請(qǐng)讀者先不要沮喪,我們自有對(duì)策,不過(guò)要看讀者慧根了^_^!。前文“依賴(lài)隔離”提到,我們可以通過(guò)一定手段,把jni、android依賴(lài)隔離掉。咦?咱們的代碼是不是有救了?之后的文章,筆者會(huì)詳細(xì)給大家講解一下。

單元測(cè)試才是必要的

經(jīng)過(guò)筆者指點(diǎn),可能有讀者蠢蠢欲動(dòng)去嘗試集成測(cè)試了.....且慢!說(shuō)好的單元測(cè)試呢?集成測(cè)試看起來(lái)簡(jiǎn)單,實(shí)際上由于依賴(lài)過(guò)多,很多時(shí)候很麻煩,而且運(yùn)行慢;相比之下,單元測(cè)試則小巧、靈活得多,運(yùn)行快,快速發(fā)現(xiàn)bug。在這方面,有一個(gè)理論Test Pyramid

示意圖中,左箭頭表示速度,右箭頭表示開(kāi)發(fā)成??梢钥吹剑瑔卧獪y(cè)試速度比集成測(cè)試(Service,也叫Integration)、UI測(cè)試要快,并且開(kāi)發(fā)成本也是最低。Test Pyramid告訴我們,應(yīng)該花大部分精力去寫(xiě)單元測(cè)試,其次才是集成測(cè)試、UI測(cè)試。

筆者建議,還是先老老實(shí)實(shí)做單元測(cè)試,有時(shí)間精力再做集成測(cè)試。

小結(jié)

本文介紹了幾個(gè)單元測(cè)試框架,介紹了junit、mockito初步使用,闡述了依賴(lài)隔離、mocking的概念,解答了"robolectric、junit能否請(qǐng)求網(wǎng)絡(luò)"問(wèn)題。結(jié)合閱讀《談?wù)劄槭裁磳?xiě)單元測(cè)試》,想必讀者對(duì)單元測(cè)試有了一個(gè)初步的了解。

如果讀者問(wèn)筆者:“我的是小項(xiàng)目,是否有必要做單元測(cè)試?” 我很肯定地回答,任何項(xiàng)目都有必要做單元測(cè)試。至于單元測(cè)試是否耗費(fèi)很多時(shí)間,或者效果不顯著,這要看使用者的編程經(jīng)驗(yàn)了,不能一概而論。

最后,叮囑讀者多敲代碼,真槍實(shí)彈地實(shí)踐單元測(cè)試??梢詮墓卷?xiàng)目小規(guī)模使用,形成自己?jiǎn)卧獪y(cè)試風(fēng)格后,就可以跟大范圍地推廣了。歡迎在本文留言討論!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/65110.html

相關(guān)文章

  • Android單元測(cè)試 - 幾個(gè)重要問(wèn)題

    摘要:言歸正傳,上一篇文章單元測(cè)試如何開(kāi)始介紹了幾款單元測(cè)試框架基本用法依賴(lài)隔離概念,本篇主要解答單元測(cè)試中幾個(gè)重要問(wèn)題。在單元測(cè)試交流微信群,很多新進(jìn)來(lái)的小伙伴,都會(huì)幾個(gè)大同小異的問(wèn)題。 showImg(/img/bVEpaD?w=1080&h=715); 原文鏈接:http://www.jianshu.com/p/f5d197a4d83a 前言 已經(jīng)一個(gè)月沒(méi)寫(xiě)文章了,由于9月份在plan...

    ChristmasBoy 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<