首先是單元測試很重要!很重要!很重要!
目前主流的是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窗口看見結果,如果是儀器化測試的話需要選擇運行的設備,然後執行以下操作
- 向模擬器(真機)安裝兩個apk,分別是app-debug.apk和app-debug-androidTest.apk,instrumented測試相關的邏輯在app-debug-androidTest.apk中。
- 安裝完這兩個apk後,通過am instrument命令運行instrumented測試用例
- 運行之後就可以在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);
}