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

什么是單元測試

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

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

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

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

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

單元測試真的這么雞肋么?No,No,No?。?!

試想

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

  2. 重構(gòu)代碼的時(shí)候,被bug淹沒。造成你持續(xù)不斷的改bug,持續(xù)不斷的加班。

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

    。。。

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

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

  1. 幫助理解需求

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

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

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

  3. 測試成本低

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

  4. 反饋速度快

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

  5. 利于重構(gòu)

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

  6. 文檔作用

    單元測試提供了被測單元的使用場景,起到了使用文檔的作用。

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

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

為什么要做單元測試?

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

Android 單元測試分類

Android 單元測試分為兩大類:

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

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

  2. Instrumented tests:

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

Android Junit + Mockito + Powermock單元測試方案

Junit + Mockito + Powermock簡介

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

Junit + Mockito + Powermock使用

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

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

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

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. 測試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. 測試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. 測試Public 返回Void 方法

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

    @Test
    public void testAddPublicStaicMethod() throws Exception {
        PowerMockito.mockStatic(Calculate.class);
    
        PowerMockito.when(Calculate.class, "addPublicStaticMethod", anyInt(), anyInt())
                .thenReturn(0)
                .thenReturn(1);
    }
    
  5. 測試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. 測試Private 方法

     @Test
    public void testAddPrivateMethod() throws Exception {
        PowerMockito.mockStatic(Calc.class);
    
        //when
        PowerMockito.when(mCalculate,"addPrivateMethod",anyInt(),anyInt())
                .thenReturn(0)
                .thenReturn(1);
        
    }
    
  7. 測試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();
    }
}

測試類

@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)行單元測試和UI測試
  6. Android單元測試只看這一篇就夠了
  7. 代碼覆蓋率淺談
  8. PowerMock單元測試踩坑與總結(jié)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容