Android單元測試——Junit+Mock

首先是單元測試很重要!很重要!很重要!

目前主流的是Junit4 來進行Java的單元測試

首先需要導入的包有

import org.junit.Test;     

import static org.junit.Assert.*;//引入斷言

不同於Junit3,測試類不需要再繼承TestCase類,可以直接聲明,此外,測試方法也不需要再以test開頭,但是爲了方便使用,最好以test<TestMethod>的形式來命名,例如,要測試add方法是否正確,測試方法名最好爲​testAdd​這種(如果使用Android Studio的話可以自動生成)。

關於Junit4的一些重要的註解

用來標記測試方法,在Junit4中規定所有的測試方法必須加上該註解。
@Before

在任何一個單元測試方法執行之前都要執行的代碼,主要做一些初始化的工作
@After

在任何一個單元測試方法執行之後都要執行的代碼,主要做一些收尾工作
@BeforeClass

public static void xxxx()//必須聲明爲該形式

在所有的單元測試執行之前執行的代碼,僅執行一次
@AfterClass

public static void xxxx() //必須聲明爲該形式

在所有的單元測試都執行完後執行的代碼,僅執行一次
@Ignore

用來標記某測試方法不參與此次的測試,在運行結束後會通知有幾個測試被忽略


整個測試的執行順序爲

@BeforeClass->(@Before->@Test->@After)->@AfterClass

             |_______________________| 

             這裏對於每個測試方法都會執行一次

關於Junit4中的一些斷言

檢查對象是否爲空
assert(Not)Null(java.lang.Object object)

檢查值是否相等
assertEquals()

檢查對象是否相等
assert(Not)Same(java.lang.Object expected, java.lang.Object actual)

檢查條件是否爲真
assertTrue(False)(boolean condition)

對於@Test有一些額外的參數可以使用

@Test(expected = Exception.class)

//測試方法若沒有拋出Annotation中的Exception類型(子類也可以)->失敗



@Test(timeout=100)

//一般用來性能測試,如果方法耗時超過了timeout會導致失敗

關於儀器化測試以及本地測試

在Android Studio中創建工程時已經提供了本地測試和儀器化測試兩種測試,分別在

src/androidTest     //儀器化測試



src/test            //本地測試

  • 關於二者的區別:

如果測試用例需要訪問儀器(instrumentation)信息(如應用程序的 Context ),或者需要 Android 框架組件的真正實現(如 Parcelable 或 SharedPreferences 對象),那麼應該創建儀器化單元測試,由於要跑到真機或模擬器上,所以會慢一些。

在AS中運行時只需要到相關的測試方法上點擊右鍵即可,如果是本地化測試的話一般可以直接在Run窗口看見結果,如果是儀器化測試的話需要選擇運行的設備,然後執行以下操作

  1. 向模擬器(真機)安裝兩個apk,分別是app-debug.apk和app-debug-androidTest.apk,instrumented測試相關的邏輯在app-debug-androidTest.apk中。
  2. 安裝完這兩個apk後,通過​am instrument​命令運行instrumented測試用例
  3. 運行之後就可以在Run窗口看見結果

But But 一般來說儀器化測試會很慢(目前還沒有感受到,可能之後會體會到),因此嘞,如何既能夠擁有本地化測試的速度,又可以擁有儀器化測試的效果嘞?這就需要用到模擬技術,下面介紹幾種比較流行的模擬技術

Mock技術

我們知道在調用某個單元測試方法時,這個方法可能會對其他的對象產生了許多依賴,參考如下的例子

我們在對BankService的某個方法進行測試時,可能會用到BankDao,AccountService,AuthService,

一般情況下,我們可以採用的方法是把這幾個類都創建出來,然後就和實際運行情況一樣了,但是可以想象如果在依賴很複雜的情況下,會非常的麻煩,與單元測試的原本意圖不符,這個時候,就可以使用Mock來創建MockObject。

 

如上的例子在使用mock之後就變爲

我們不需要真正的創建實例,只需要模擬對應的方法即可。

Mocktio

在使用之前需要自build.gradel加入以下依賴

testImplementation 'org.mockito:mockito-core:2.19.0'

同時需要在文件中添加如下包

import static org.mockito.Mockito.*;

爲測試類加上以下註解

@RunWith(MockitoJUnitRunner.class)

對於Mockito主要有如下的幾種語法

  • 對象聲明

mockito中存在着兩種類型,一種是mock,一種是spy

在使用上對於mock一般是,加入要mock類A,使用方法是

A mockA = mock(List.class)//此時只是模擬了類A,並沒有真正的創建A

對於spy使用方法是

A a = new A();

A spyA = spy(a);

關於二者的區別https://codeday.me/bug/20180907/245994.html

  • 函數調用
when(x).thenReturn(a).thenReturn(b).......//表示當x第一次發生時返回a,第二次發生時返回b,以此類推,

//這裏的x表示調用的函數(函數+參數唯一標誌), a表示返回值,從這裏也可以發現,使用mock並沒有真正的調用函數,只是模擬了返回值


doReturn().when();     //與上述類似,區別在於上述是mock對象,這個是spy對象,同理,也是模擬了返回值


doCallRealMethod().when();//針對mock對象,這裏是真正調用了該方法
  • 拋出異常
doThrow(new Exception).when(x); //表示當x發生的時候拋出異常
  • 返回值爲void
doNothing().when()
  • 校驗mock方法的調用
verigy(《Object》, 《time》).《function》(《param》);

//《Object》表示校驗的對象

//《function》(《param》)表示校驗的行爲

//《time》表示校驗的條件,例如

//-atLeastOnce():表示function至少被調用了一次

//-times(x):表示function被調用了x次

//-never():表示function從未被調用

Talk is cheap, show me your code!

mock使用實例

@Test

public void configMockObject() {

    List mockedList = mock(List.class);



    // 我們定製了當調用 mockedList.add("one") 時, 返回 true

    when(mockedList.add("one")).thenReturn(true);

    // 當調用 mockedList.size() 時, 返回 1

    when(mockedList.size()).thenReturn(1);



    Assert.assertTrue(mockedList.add("one"));

    // 因爲我們沒有定製 add("two"), 因此返回默認值, 即 false.

    Assert.assertFalse(mockedList.add("two"));

    Assert.assertEquals(mockedList.size(), 1);



    Iterator i = mock(Iterator.class);

    when(i.next()).thenReturn("Hello,").thenReturn("Mockito!");

    String result = i.next() + " " + i.next();

    //assert

    Assert.assertEquals("Hello, Mockito!", result);

}

mock校驗實例

@Testpublic void testVerify() {

    List mockedList = mock(List.class);

    mockedList.add("one");

    mockedList.add("two");

    mockedList.add("three times");

    mockedList.add("three times");

    mockedList.add("three times");

    when(mockedList.size()).thenReturn(5);

    Assert.assertEquals(mockedList.size(), 5);



    verify(mockedList, atLeastOnce()).add("one");

    verify(mockedList, times(1)).add("two");

    verify(mockedList, times(3)).add("three times");

    verify(mockedList, never()).isEmpty();

}

spy使用實例

@Testpublic void testSpy() {

    List list = new LinkedList();

    List spy = spy(list);



    // 對 spy.size() 進行定製.

    when(spy.size()).thenReturn(100);



    spy.add("one");

    spy.add("two");



    // 因爲我們沒有對 get(0), get(1) 方法進行定製,

    // 因此這些調用其實是調用的真實對象的方法.

    Assert.assertEquals(spy.get(0), "one");

    Assert.assertEquals(spy.get(1), "two");



    Assert.assertEquals(spy.size(), 100);

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章