Android 開(kāi)發(fā)如何進(jìn)行單元測(cè)試

什么是單元測(cè)試

單元測(cè)試是由一組獨(dú)立的測(cè)試構(gòu)成腾供,每個(gè)測(cè)試針對(duì)軟件中的一個(gè)單獨(dú)的程序單元疹启。單元測(cè)試并非檢查程序單元直接是否能夠合作良好,而是檢查單個(gè)程序單元的行為是否正確玻驻。

事實(shí)上曙聂,單元測(cè)試是一種驗(yàn)證行為,測(cè)試和驗(yàn)證程序中的每一項(xiàng)的正確性苦囱。

為什么要進(jìn)行單元測(cè)試

對(duì)于單元測(cè)試嗅绸,人們往往存在很多誤解:

  1. 浪費(fèi)時(shí)間太多,本身項(xiàng)目的時(shí)間就很緊張撕彤,沒(méi)有時(shí)間去寫(xiě)單元測(cè)試鱼鸠。
  2. 過(guò)度的依賴測(cè)試人員,認(rèn)為軟件開(kāi)發(fā)人員不應(yīng)該參與單元測(cè)試羹铅。
  3. 認(rèn)為單元測(cè)試不必要瞧柔,代碼寫(xiě)得很好了,no bug睦裳,no warning造锅。
  4. 老代碼結(jié)構(gòu)混亂,耦合度高廉邑,為了寫(xiě)單元測(cè)試修改代碼結(jié)構(gòu)哥蔚,意義不大倒谷,投入跟產(chǎn)出不成比例。

單元測(cè)試真的這么雞肋么糙箍?No渤愁,No,NoI詈弧6陡瘛!

試想

  1. 測(cè)試人員給你報(bào)了一個(gè)bug咕晋,但是由于之后的merge失誤導(dǎo)致代碼丟失雹拄,或者別人修改代碼導(dǎo)致這個(gè)bug再次復(fù)現(xiàn)。

  2. 重構(gòu)代碼的時(shí)候掌呜,被bug淹沒(méi)滓玖。造成你持續(xù)不斷的改bug,持續(xù)不斷的加班质蕉。

  3. 明明很正常的功能势篡,怎么現(xiàn)在突然不能用了?是接口的問(wèn)題模暗,還是有人修改了這個(gè)功能的邏輯禁悠?

    。兑宇。碍侦。

如果你也經(jīng)常遇到這些困惑,那么你就需要對(duì)項(xiàng)目進(jìn)行單元測(cè)試了顾孽。

因?yàn)?strong>單元測(cè)試具有以下優(yōu)勢(shì):

  1. 幫助理解需求

    單元測(cè)試應(yīng)該反映Use Case,把被測(cè)單元當(dāng)成黑盒測(cè)試其外部行為比规。

  2. 提高實(shí)現(xiàn)質(zhì)量

    單元測(cè)試不保證程序做正確的事若厚,但能幫助保證程序正確地做事,從而提高實(shí)現(xiàn)質(zhì)量蜒什。

  3. 測(cè)試成本低

    相比集成測(cè)試测秸、驗(yàn)收測(cè)試,單元測(cè)試所依賴的外部環(huán)境少灾常,自動(dòng)化程度高霎冯,時(shí)間短,節(jié)約了測(cè)試成本钞瀑。

  4. 反饋速度快

    單元測(cè)試提供快速反饋沈撞,把bug消滅在開(kāi)發(fā)階段,減少問(wèn)題流到集成測(cè)試雕什、驗(yàn)收測(cè)試和用戶缠俺,降低了軟件質(zhì)量控制的成本显晶。

  5. 利于重構(gòu)

    由于有單元測(cè)試作為回歸測(cè)試用例,有助于預(yù)防在重構(gòu)過(guò)程中引入bug壹士。

  6. 文檔作用

    單元測(cè)試提供了被測(cè)單元的使用場(chǎng)景磷雇,起到了使用文檔的作用。

  7. 對(duì)設(shè)計(jì)的反饋

    一個(gè)模塊很難進(jìn)行單元測(cè)試通常是不良設(shè)計(jì)的信號(hào)躏救,單元測(cè)試可以反過(guò)來(lái)指導(dǎo)設(shè)計(jì)出高內(nèi)聚唯笙、低耦合的模塊。

為什么要做單元測(cè)試盒使?

怎么進(jìn)行單元測(cè)試

Android 單元測(cè)試分類

Android 單元測(cè)試分為兩大類:

app/src
     ├── androidTestjava (Instrumented 單元測(cè)試崩掘、UI測(cè)試)
     ├── main/java (業(yè)務(wù)代碼)
     └── test/java  (Local 單元測(cè)試)
  1. Local test:

    運(yùn)行在本地的JVM虛擬機(jī)上,不依賴Android框架忠怖。

  2. Instrumented tests:

    通過(guò)Android系統(tǒng)的Instrumented測(cè)試框架呢堰,運(yùn)行測(cè)試代碼在真實(shí)手機(jī)上。

Android Junit + Mockito + Powermock單元測(cè)試方案

Junit + Mockito + Powermock簡(jiǎn)介

Junit 是一個(gè)Java語(yǔ)言的單元測(cè)試框架
Mockito 是一個(gè)Mock框架凡泣,我們可以通過(guò)Mockito框架創(chuàng)建配置mock對(duì)象枉疼。
Powermock 可以針對(duì)static,final鞋拟,private方法進(jìn)行mock

Junit + Mockito + Powermock使用

強(qiáng)烈建議你熟讀以下內(nèi)容骂维,來(lái)熟悉Junit + Mockito + Powermock的使用。

  1. Mockito 中文文檔 ( 2.0.26 beta )
  2. Mockito reference documentation
  3. powermock wiki
  4. Unit tests with Mockito - Tutorial

比如說(shuō)我們要對(duì)Calculate類進(jìn)行單元測(cè)試

public class Calculate {

    private int mPrivate;

    private final int mPrivateFinal = 0;

    private static int mPrivateStatic = 0;

    private static final int mPrivateStaticFinal = 0;

    public int mPublic;

    public final int mPublicFinal = 0;

    public static int mPublicStatic = 0;

    public static final int mPublicStaticFinal = 0;

    public void voidPublicMethod(int a, int b) {
        return;
    }

    public int addPublicMethod(int a, int b) {
        return a + b;
    }

    private int addPrivateMethod(int a, int b) {
        return a + b;
    }

    public static int addPublicStaticMethod(int a, int b) {
        return a + b;
    }

    private static int addPrivateStaticMethod(int a, int b) {
        return a + b;
    }

}
  1. 測(cè)試Public 變量

     @Test
    public void testPublicField() {
        assertEquals(mCalculate.mPublic, 0);
        assertEquals(mCalculate.mPublicFinal, 0);
        assertEquals(Calculate.mPublicStatic, 0);
        assertEquals(Calculate.mPublicStaticFinal, 0);
    
        mCalculate.mPublic = 1;
        Calculate.mPublicStatic = 2;
    
        assertEquals(mCalculate.mPublic, 1);
        assertEquals(mCalculate.mPublicFinal, 0);
        assertEquals(Calculate.mPublicStatic, 2);
    }
    
  2. 測(cè)試Public 方法

      @Test
    public void testAddPublicMethod() {
        //when
        when(mCalculate.addPublicMethod(anyInt(), anyInt()))
                .thenReturn(0)
                .thenReturn(1);
    
        //call method
        for (int i = 0; i < 2; i++) {
    
            //verify
            assertEquals(mCalculate.addPublicMethod(i, i), i);
        }
    
        //verify
        verify(mCalculate, times(2)).addPublicMethod(anyInt(), anyInt());
        verify(mCalculate, atLeast(1)).addPublicMethod(anyInt(), anyInt());
        verify(mCalculate, atLeastOnce()).addPublicMethod(anyInt(), anyInt());
        verify(mCalculate, atMost(2)).addPublicMethod(anyInt(), anyInt());
    }
    
  3. 測(cè)試Public 返回Void 方法

    @Test
    public void testAddPublicVoidMethod() {
        //when
        doNothing().when(mCalculate).voidPublicMethod(anyInt(), anyInt());
    
    }
    
  4. 測(cè)試Public Static 方法

    @Test
    public void testAddPublicStaicMethod() throws Exception {
        PowerMockito.mockStatic(Calculate.class);
    
        PowerMockito.when(Calculate.class, "addPublicStaticMethod", anyInt(), anyInt())
                .thenReturn(0)
                .thenReturn(1);
    }
    
  5. 測(cè)試Private Static 變量

     @Test
    public void testPrivate() throws IllegalAccessException {
        PowerMockito.mockStatic(Calculate.class);
    
        assertEquals(Whitebox.getField(Calculate.class, "mPrivate").getInt(mCalculate), 0);
        assertEquals(Whitebox.getField(Calculate.class, "mPrivateFinal").getInt(mCalculate), 0);
        assertEquals(Whitebox.getField(Calculate.class, "mPrivateStatic").getInt(null), 0);
        assertEquals(Whitebox.getField(Calculate.class, "mPrivateStaticFinal").getInt(null), 0);
    
    
        Whitebox.setInternalState(mCalculate, "mPrivate", 1);
        Whitebox.setInternalState(Calculate.class, "mPrivateStatic", 1, Calculate.class);
    
        assertEquals(Whitebox.getField(Calculate.class, "mPrivate").getInt(mCalculate), 1);
        assertEquals(Whitebox.getField(Calculate.class, "mPrivateFinal").getInt(mCalculate), 0);
        assertEquals(Whitebox.getField(Calculate.class, "mPrivateStatic").getInt(null), 1);
        assertEquals(Whitebox.getField(Calculate.class, "mPrivateStaticFinal").getInt(null), 0);
    }
    
  6. 測(cè)試Private 方法

     @Test
    public void testAddPrivateMethod() throws Exception {
        PowerMockito.mockStatic(Calc.class);
    
        //when
        PowerMockito.when(mCalculate,"addPrivateMethod",anyInt(),anyInt())
                .thenReturn(0)
                .thenReturn(1);
        
    }
    
  7. 測(cè)試Private static 方法

    @Test
    public void testAddPrivateStaicMethod() throws Exception {
        PowerMockito.mockStatic(Calculate.class);
    
        PowerMockito.when(Calculate.class, "addPrivateStaticMethod", anyInt(), anyInt())
                .thenReturn(0)
                .thenReturn(1);
    }
    

對(duì)單例進(jìn)行mock

比如對(duì)以下代碼進(jìn)行mock

public class Singleton {

    public String PUBLIC_STR = "public_str";

    private static class ServiceSingleton {
        private static final Singleton SINGLE = new Singleton();
    }

    public static Singleton get() {
        return ServiceSingleton.SINGLE;
    }

    public String getPublicStr() {
        return PUBLIC_STR;
    }

}
public class SingletonUse {
    private static final String PUBLIC_STR = "public_str";

    private static class ServiceSingleton {
        private static final SingletonUse SINGLE = new SingletonUse();
    }

    public static SingletonUse get() {
        return ServiceSingleton.SINGLE;
    }

    public String getUsePublicStr() {
        return Singleton.get().getPublicStr();
    }
}

測(cè)試類

@RunWith(PowerMockRunner.class)
@PrepareForTest({Singleton.class, SingletonUse.class})
public class SingletonUseTest {

    private SingletonUse singletonUse;

    @Before
    public void setUp() throws Exception {
        Singleton singleton = PowerMockito.mock(Singleton.class);
        PowerMockito.mockStatic(Singleton.class);
        PowerMockito.doReturn(singleton).when(Singleton.class, "get");

        singletonUse = PowerMockito.mock(SingletonUse.class);
        PowerMockito.mockStatic(SingletonUse.class);
        PowerMockito.doReturn(singletonUse).when(SingletonUse.class, "get");
    }

    @Test
    public void getUsePublicStr() throws Exception {
        PowerMockito.doReturn("123456").when(singletonUse,"getUsePublicStr");
    }
}

參考資料

  1. Unit testing support
  2. junit4
  3. mockito
  4. powermock
  5. 在Android Studio中進(jìn)行單元測(cè)試和UI測(cè)試
  6. Android單元測(cè)試只看這一篇就夠了
  7. 代碼覆蓋率淺談
  8. PowerMock單元測(cè)試踩坑與總結(jié)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末贺纲,一起剝皮案震驚了整個(gè)濱河市航闺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌猴誊,老刑警劉巖潦刃,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異懈叹,居然都是意外死亡乖杠,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門澄成,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)胧洒,“玉大人,你說(shuō)我怎么就攤上這事墨状∥缆” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵肾砂,是天一觀的道長(zhǎng)列赎。 經(jīng)常有香客問(wèn)我,道長(zhǎng)镐确,這世上最難降的妖魔是什么粥谬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任肛根,我火速辦了婚禮,結(jié)果婚禮上漏策,老公的妹妹穿的比我還像新娘派哲。我一直安慰自己,他們只是感情好掺喻,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布芭届。 她就那樣靜靜地躺著,像睡著了一般感耙。 火紅的嫁衣襯著肌膚如雪褂乍。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天即硼,我揣著相機(jī)與錄音逃片,去河邊找鬼。 笑死只酥,一個(gè)胖子當(dāng)著我的面吹牛褥实,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播裂允,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼损离,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了绝编?” 一聲冷哼從身側(cè)響起僻澎,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎十饥,沒(méi)想到半個(gè)月后窟勃,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡逗堵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年秉氧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砸捏。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谬运,死狀恐怖隙赁,靈堂內(nèi)的尸體忽然破棺而出垦藏,到底是詐尸還是另有隱情,我是刑警寧澤伞访,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布掂骏,位于F島的核電站,受9級(jí)特大地震影響厚掷,放射性物質(zhì)發(fā)生泄漏弟灼。R本人自食惡果不足惜级解,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望田绑。 院中可真熱鬧勤哗,春花似錦、人聲如沸掩驱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)欧穴。三九已至民逼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涮帘,已是汗流浹背拼苍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留调缨,地道東北人疮鲫。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像同蜻,于是被迫代替她去往敵國(guó)和親棚点。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

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