對Android有依賴的單元測試如何寫栖袋?怎樣脫離真機與模擬器叹卷?本文將會對Java測試框架mockito做詳細介紹舒岸。
若有錯漏绅作,煩請斧正。轉(zhuǎn)載請注明出處蛾派。
- 作者:程序引力 | 謝一 (Evan Xie)
- 郵箱:evanyixie@gmail.com
前言
在單元測試基礎篇中俄认,介紹了單元測試的基礎以及JUnit以及AndroidJUnintRunner,對于測試不依賴于Android的Java代碼碍脏,可以僅使用JUnit進行測試梭依。但若需要測試的代碼依賴于Android,則需要結(jié)合AndroidJUnitRunner并運行在真機或模擬器上才行典尾。
不知開發(fā)者有沒有一個疑問役拴?能否有一種方式可以讓對Android存在依賴的測試代碼也直接運行于本地的JVM中?答案是肯定的钾埂。mockito則提供了這樣的能力河闰,能夠脫離真機與模擬器運行對安卓有依賴的測試代碼。
Mockito概述
在實際的工程中褥紫,待測試的代碼往往不是孤立存在的姜性,其中的方法或變量可能依賴外部的變量。為此髓考,為了測試某一部分代碼部念,需要將其他被依賴的代碼關聯(lián)進來。更為可能的情況是氨菇,這些被依賴的代碼可能還存在更多的依賴儡炼,這就導致開發(fā)者為了測試某一小部分的代碼,而不得不與龐大的其他代碼相關聯(lián)查蓉。為了解決這個問題乌询,Mockito應運而生。
Mockito是一款Java測試框架豌研,它通過構(gòu)造一些‘假’的對象妹田,來將測試代碼與其依賴隔離唬党,進而提高了測試的易用性與運行效率。
Mockito主要有兩個作用:
- 驗證某個對象的行為鬼佣。
- 驗證某個對象的行為的調(diào)用次數(shù)驶拱。
Mockito優(yōu)點:
- 能夠?qū)⒋郎y試代碼與其依賴進行隔離
- 讓一些對Android存在一定依賴的測試代碼,運行在本地JVM上
適合使用Mockito的場景:
- 待測試代碼對Android有較小的依賴
- 開發(fā)者希望待測試代碼與其依賴隔離開
引入Mockito
使用Mockito前需要引入相應的庫沮趣,除了JUnit框架外屯烦,還需在模塊目錄(如APP)的build.gradle中,添加如下依賴:
dependencies {
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:1.10.19'
androidTestImplementation 'org.mockito:mockito-core:1.10.19'
}
同步后即可引入Mockito房铭。
Mockito的基本對象
Mockito提供了兩種基本對象,分別是mock對象與spy對象温眉。
- mock對象:完全虛構(gòu)的對象缸匪,除了自定義的行為外,無其他行為类溢。
- spy對象:部分虛構(gòu)的對象凌蔬,除了自定義行為外,其他行為參考真實對象的行為闯冷。
可以這么理解砂心,spy對象是在真實對象的基礎上,自定義了部分行為蛇耀,其他行就是原本真實對象的行為辩诞。而mock對象是完全虛構(gòu),完全自定義的纺涤,對于沒有定位的行為译暂,就僅有默認值。
撰寫測試代碼總體步驟
撰寫基于Mockito框架的測試代碼撩炊,主要步驟為:
- 構(gòu)造mock/spy對象
- 定義對象的行為
- 運行測試代碼
- 校驗測試代碼運行結(jié)果與預期結(jié)果
Mock對象簡單用法
構(gòu)造mock對象
構(gòu)造mock對象的方式有兩類外永,一類是通過mock方法進行創(chuàng)建,另一類是通過@Mock注解的方式創(chuàng)建拧咳。對于后者伯顶,又分為三種不同的方式
通過mock方法創(chuàng)建對象:
@Before
public void setUp() throws Exception {
mockedList = mock(ArrayList.class);
}
對于setUp()方法,由于添加了@Before注解骆膝,故該方法在所有@Test方法之前執(zhí)行祭衩。在該方法中,將需要被構(gòu)造的類傳入mock方法中谭网,能夠創(chuàng)建出mock對象汪厨。
通過@Mock注解的方式創(chuàng)建對象
- 方法1:使用前調(diào)用iniMocks方法:
@Mock
private ArrayList mockedList;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
- 方法2:通過@RunWith注解:
@RunWith(MockitoJUnitRunner.class)
public class MockitoJUnitRunnerTest {
@Mock
AccountData accountData;
}
- 方法3:通過MockitoRule
public class MockitoRuleTest {
@Mock
AccountData accountData;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
}
不管通過何種方式,創(chuàng)建出mock對象愉择,是執(zhí)行后續(xù)測試流程的前提劫乱。
定義mock對象行為
when(mockedList.get(0)).thenReturn("first");
定義mock對象行為的語法非常通俗易懂织中,其語法為當(when)調(diào)用什么時,返回(thenReturn)什么衷戈。對于上面的例子狭吼,即當調(diào)用mockedList.get(0)時,返回“first".
運行測試代碼
構(gòu)造mock對象殖妇,并且定義了該對象的行為后刁笙,即可運行該對象的相應方法與其他需要被測試的方法。在該簡單的例子中谦趣,該步驟的代碼為:
String res = mockedList.get(0);
校驗測試代碼運行結(jié)果與預期結(jié)果
得到運行結(jié)果后疲吸,可以通過斷言與預期結(jié)構(gòu)比較,得到測試結(jié)果前鹅。
assertEquals(res,"first")
本例完整代碼
@RunWith(MockitoJUnitRunner.class)
public class MockitoTest {
@Mock
ArrayList mockedList; //創(chuàng)建mock對象
@Before
public void setUp(){
when(mockedList.get(0)).thenReturn("first"); //定義mock行為
}
@Test
public void testMockito(){
String res = (String)mockedList.get(0); //調(diào)用mock對象方法
assertEquals(res, "first"); //比較實際結(jié)果與預期
}
}
Mock對象存在安卓依賴的用法
對于存在Android依賴的情況摘悴,其測試代碼的基本思路也與上文中的例子是一致的。也是在創(chuàng)建對象后舰绘,定義對象行為蹂喻,然后調(diào)用對象方法并與預期比較。在下面的例子中捂寿,就不分布介紹了口四,具體看代碼右側(cè)的注釋。
以安卓官網(wǎng)一個的例子看:
@RunWith(MockitoJUnitRunner.class) //聲明使用Mockito
public class UnitTestSample {
private static final String FAKE_STRING = "HELLO WORLD";
@Mock
Context mMockContext; //mock構(gòu)造一個context對象
@Test
public void readStringFromContext_LocalizedString() {
// 定義該mock對象的行為秦陋,即通過id或者字符串
when(mMockContext.getString(R.string.hello_world)).thenReturn(FAKE_STRING);
// 將該mockContext傳入某個類蔓彩,安卓場景中一般都會傳入context
ClassUnderTest myObjectUnderTest = new ClassUnderTest(mMockContext);
// 運行測試代碼,獲取字符串
String result = myObjectUnderTest.getHelloWorldString();
// 校驗實際結(jié)果與預期結(jié)果是否一致
assertThat(result, is(FAKE_STRING));
}
}
Verify語句的用法
在前面的例子中踱侣,主要是定義mock對象的行為后粪小,驗證其行為與預期是否一致。但存在著另外一種情況抡句,開發(fā)者希望驗證mock對象的某個方法是否調(diào)用探膊,以及調(diào)用的其他情況。那么可以使用Mockito提供的verify方法待榔。
verify語句使用的形式為:
verify(mock對象).方法(參數(shù))
如果該mock對象的該方法被調(diào)用過(調(diào)用時傳入的參數(shù)也一樣)逞壁,則該測試通過。
verify的簡單例子
public class MockitoTest {
@Mock
ArrayList mockedList;
@Before
public void setUp(){
//when(mockedList.get(0)).thenReturn("first"); //即使注釋掉定義定位的代碼
}
@Test
public void testMockito(){
String res = (String)mockedList.get(0); //調(diào)用mock對象的get方法
verify(mockedList).get(0); //驗證通過
verify(mockedList).get(1); //驗證不通過
}
}
在這個例子中锐锣,盡管沒有定義mock對象的行為腌闯,但只要調(diào)用了該方法(此時get返回null),調(diào)用verify方法也能使驗證通過雕憔。若參數(shù)不一致姿骏,則驗證不通過。
verify方法的其他用法
除了簡單驗證mock對象的某個方法是否調(diào)用斤彼,verify還可以驗證該方法的調(diào)用情況分瘦。
verify(mockedList).get(0); //驗證方法是否調(diào)用蘸泻,且參數(shù)傳入的是0
verify(mockedList, times(1)).get(0); //驗證方法是否調(diào)用且只調(diào)用了1次
verify(mockedList, never()).get(0); //驗證方法是否沒有被調(diào)用過
verify(mockedList, atLeast(2)).get(0); //驗證方法是否調(diào)用且調(diào)用2次以上
verify(mockedList, atMost(5)).get(0); //驗證方法是否調(diào)用且最多調(diào)用5次
verify(mockedList).get(anyInt()); //驗證方法是否調(diào)用,且傳入的參數(shù)是任意整型數(shù)
spy對象的用法
spy對象與mock對象的區(qū)別在于:
- mock對象:通過類進行創(chuàng)建嘲玫,是完全虛構(gòu)的對象悦施,除了自定義的行為外,無其他行為去团。
- spy對象:通過對象進行創(chuàng)建抡诞,是部分虛構(gòu)的對象,除了自定義行為外土陪,其他行為參考真實對象的行為昼汗。
也就是說,對于spy對象旺坠,除了自定義的行為外乔遮,均把它當做原本的那個對象處理。簡單的例子如下:
@RunWith(MockitoJUnitRunner.class)
public class AssertEquals {
@Test
public void testMockito(){
ArrayList<Integer> integers = new ArrayList<>();
ArrayList<Integer> spyList = spy(integers); //通過真實的對象創(chuàng)建spy對象
spyList.add(1); //真實對象添加了一個元素
System.out.println(spyList.get(0)); //查看其元素值取刃,返回1
when(spyList.size()).thenReturn(100); //定義其行為,虛構(gòu)其size為100
System.out.println(spyList.size()); //查看其size,返回100
when(spyList.get(0)).thenReturn(2); //定義其行為出刷,虛構(gòu)其第一個元素值
System.out.println(spyList.get(0)); //返回2璧疗,即虛構(gòu)的行為會覆蓋原有真實的行為
}
}
從上面的例子中可知,spy僅僅是對真實對象的行為進行部分虛構(gòu)馁龟,虛構(gòu)的部分可以覆蓋原來的部分崩侠。
總結(jié)
Android開發(fā)者應該了解到,在撰寫單元測試之前坷檩,需要考慮自己的測試場景是什么却音,依賴是什么,需求是什么矢炼。如果需要測試的代碼對Android有一定的依賴系瓢,同時又希望這樣的單元測試是運行在本地JVM中,則可以選擇mockito框架進行測試句灌。
Mockito框架的使用方法也很簡單夷陋,主要分為四步:
- 構(gòu)造mock/spy對象
- 定義mock對象的行為
- 運行測試代碼
- 校驗測試代碼運行結(jié)果與預期結(jié)果
只要記住這四步,同時查閱Mockito的API文檔胰锌,了解它支持定義哪些行為骗绕,即可寫出符合實際需要的單元測試用例。
若你喜歡本文或覺得有所幫助资昧,請點贊或關注酬土。
你的支持是對筆者最大的鼓勵與肯定。比芯~