原文鏈接:川峰-Android單元測(cè)試學(xué)習(xí)總結(jié)
Android單元測(cè)試主要分為以下兩種
- 本地單元測(cè)試(Junit Test)每强, 本地單元測(cè)試是純java代碼的測(cè)試吠各,只運(yùn)行在本地電腦的JVM環(huán)境上布朦,不依賴于Android框架的任何api, 因此執(zhí)行速度快,效率較高,但是無(wú)法測(cè)試Android相關(guān)的代碼抛虫。
- 儀器化測(cè)試(Android Test),是針對(duì)Android相關(guān)代碼的測(cè)試简僧,需要運(yùn)行在真機(jī)設(shè)備或模擬器上建椰,運(yùn)行速度較慢,但是可以測(cè)試UI的交互以及對(duì)設(shè)備信息的訪問(wèn)岛马,得到接近真實(shí)的測(cè)試結(jié)果棉姐。
在Android Studio中新建一個(gè)項(xiàng)目的時(shí)候屠列,app的gradle中會(huì)默認(rèn)添加單元測(cè)試的相關(guān)依賴庫(kù):
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
其中testImplementation添加的依賴就是本地化測(cè)試庫(kù), androidTestImplementation 添加的依賴則是Android環(huán)境下的測(cè)試庫(kù)伞矩,同時(shí)笛洛,在項(xiàng)目的工程目錄下也會(huì)默認(rèn)創(chuàng)建好測(cè)試的目錄:
其中app/src/test/下面存放的是Junit本地測(cè)試代碼,app/src/androidTest/下面存放的是Android測(cè)試代碼乃坤。
一苛让、本地單元測(cè)試
進(jìn)行本地單元測(cè)試需要先了解一些基本的Junit注解:
注解名稱 | 含義 |
---|---|
@Test | 定義所在方法為單元測(cè)試方法,方法必須是public void |
@Before | 定義所在方法在每個(gè)測(cè)試用例執(zhí)行之前執(zhí)行一次湿诊, 用于準(zhǔn)備測(cè)試環(huán)境(如: 初始化類狱杰,讀輸入流等),在一個(gè)測(cè)試類中厅须,每個(gè)@Test方法的執(zhí)行都會(huì)觸發(fā)一次調(diào)用 |
@After | 定義所在方法在每個(gè)測(cè)試用例執(zhí)行之后執(zhí)行一次仿畸,用于清理測(cè)試環(huán)境數(shù)據(jù),在一個(gè)測(cè)試類中朗和,每個(gè)@Test方法的執(zhí)行都會(huì)觸發(fā)一次調(diào)用错沽。 |
@BeforeClass | 定義所在方法在測(cè)試類里的所有用例運(yùn)行之前運(yùn)行一次,方法必須是public static void眶拉,用于做一些耗時(shí)的初始化工作(如: 連接數(shù)據(jù)庫(kù)) |
@AfterClass | 定義所在方法在測(cè)試類里的所有用例運(yùn)行之后運(yùn)行一次甥捺,方法必須是public static void,用于清理數(shù)據(jù)(如: 斷開(kāi)數(shù)據(jù)連接) |
@Test (expected = Exception.class) | 如果該測(cè)試方法沒(méi)有拋出Annotation中的Exception類型(子類也可以)镀层,則測(cè)試失敗 |
@Test(timeout=100) | 如果該測(cè)試方法耗時(shí)超過(guò)100毫秒镰禾,則測(cè)試失敗,用于性能測(cè)試 |
@Ignore 或者 @Ignore(“太耗時(shí)”) | 忽略當(dāng)前測(cè)試方法唱逢,一般用于測(cè)試方法還沒(méi)有準(zhǔn)備好吴侦,或者太耗時(shí)之類的 |
@FixMethodOrder | 定義所在的測(cè)試類中的所有測(cè)試方法都按照固定的順序執(zhí)行,可以指定3個(gè)值坞古,分別是DEFAULT备韧、JVM、NAME_ASCENDING(字母順序) |
@RunWith | 指定測(cè)試類的測(cè)試運(yùn)行器 |
更多可以參考Junit官網(wǎng):https://junit.org/junit4/
1. 創(chuàng)建測(cè)試類
接下來(lái)就可以創(chuàng)建測(cè)試類痪枫,除了可以手動(dòng)創(chuàng)建測(cè)試類外织堂,可以利用AS快捷鍵:將光標(biāo)選中要?jiǎng)?chuàng)建測(cè)試類的類名上->按下ALT + ENTER->在彈出的彈窗中選擇Create Test
這會(huì)彈出下面的彈窗,或者鼠標(biāo)在類名上右鍵選擇菜單Go to–>Test奶陈,也會(huì)彈出下面的彈窗
勾選需要進(jìn)行測(cè)試的方法易阳,會(huì)自動(dòng)生成一個(gè)測(cè)試類:
如果勾選了@Before或@After的話也會(huì)自動(dòng)給你生成對(duì)應(yīng)的測(cè)試方法
接下來(lái)編寫(xiě)測(cè)試方法,首先在要測(cè)試的目標(biāo)類中寫(xiě)幾個(gè)業(yè)務(wù)方法:
public class SimpleClass {
public boolean isTeenager(int age) {
if (age < 15) {
return true;
}
return false;
}
public int add(int a, int b) {
return a + b;
}
public String getNameById(int id) {
if (id == 1) {
return "小明";
} else if (id == 2){
return "小紅";
}
return "";
}
}
然后吃粒,測(cè)試類:
@RunWith(JUnit4.class)
public class SimpleClassTest {
private SimpleClass simpleClass;
@Before
public void setUp() throws Exception {
simpleClass = new SimpleClass();
}
@After
public void tearDown() throws Exception {
}
@Test
public void isTeenager() {
Assert.assertFalse(simpleClass.isTeenager(20));
Assert.assertTrue(simpleClass.isTeenager(14));
}
@Test
public void add() {
Assert.assertEquals(simpleClass.add(3, 2), 5);
Assert.assertNotEquals(simpleClass.add(3, 2), 4);
}
@Test
public void getNameById() {
Assert.assertEquals(simpleClass.getNameById(1), "小明");
Assert.assertEquals(simpleClass.getNameById(2), "小紅");
Assert.assertEquals(simpleClass.getNameById(10), "");
}
}
其中setUp()是自動(dòng)生成的添加了@Before注解潦俺,這會(huì)在每個(gè)測(cè)試方法執(zhí)行前執(zhí)行,因此在這里創(chuàng)建一個(gè)目標(biāo)對(duì)象,也可以選擇添加@BeforeClass注解但這時(shí)setUp()應(yīng)該改為靜態(tài)的方法事示。然后在每個(gè)測(cè)試方法中編寫(xiě)測(cè)試用例早像,這里使用org.junit.Assert包中的斷言方法,有很多assertXXX方法肖爵,可以自己選擇用來(lái)判斷目標(biāo)方法的結(jié)果是否滿足預(yù)期卢鹦。
2. Assert類中的常用斷言方法
方法 | 含義 |
---|---|
assertNull(Object object) | 斷言對(duì)象為空 |
assertNull(String message, Object object) | 斷言對(duì)象為空,如果不為空拋出異常攜帶指定的message信息 |
assertNotNull(Object object) | 斷言對(duì)象不為空 |
assertNotNull(Object object) | 斷言對(duì)象不為空劝堪,如果為空拋出異常攜帶指定的message信息 |
assertSame(Object expected, Object actual) | 斷言兩個(gè)對(duì)象引用的是同一個(gè)對(duì)象 |
assertSame(String message, Object expected, Object actual) | 斷言兩個(gè)對(duì)象引用的是同一個(gè)對(duì)象冀自,否則拋出異常攜帶指定的message信息 |
assertNotSame(Object expected, Object actual) | 斷言兩個(gè)對(duì)象引用的不是同一個(gè)對(duì)象 |
assertNotSame(String message, Object expected, Object actual) | 斷言兩個(gè)對(duì)象引用的不是同一個(gè)對(duì)象,否則拋出異常攜帶指定的message信息 |
assertTrue(boolean condition) | 斷言結(jié)果為true |
assertTrue(String message, boolean condition) | 斷言結(jié)果為true, 為false時(shí)拋出異常攜帶指定的message信息 |
assertFalse(boolean condition) | 斷言結(jié)果為false |
assertFalse(String message, boolean condition) | 斷言結(jié)果為false, 為true時(shí)拋出異常攜帶指定的message信息 |
assertEquals(long expected, long actual) | 斷言兩個(gè)long 類型 expected 和 actual 的值相等 |
assertEquals(String message, long expected, long actual) | 斷言兩個(gè)long 類型 expected 和 actual 的值相等幅聘,如不相等則拋異常攜帶指定message信息 |
assertEquals(Object expected, Object actual) | 斷言兩個(gè)對(duì)象相等 |
assertEquals(String message, Object expected, Object actual) | 斷言兩個(gè)對(duì)象相等凡纳,如果不相等則拋出異常攜帶指定的message信息 |
assertEquals(float expected, float actual, float delta) | 斷言兩個(gè) float 類型 expect 和 actual 在 delta 偏差值下相等窃植,delta是誤差精度 |
assertEquals(String message, float expected, float actual, float delta) | 斷言兩個(gè) float 類型 expect 和 actual 在 delta 偏差值下相等帝蒿,如果不相等則拋出異常攜帶指定的message信息 |
assertEquals(double expected, double actual, double delta) | 斷言兩個(gè) double 類型 expect 和 actual 在 delta 偏差值下相等 |
assertEquals(String message, double expected,double actual, double delta) | 斷言兩個(gè) double 類型 expect 和 actual 在 delta 偏差值下相等,如果不相等則拋出異常攜帶指定的message信息 |
assertArrayEquals(T[] expected, T[] actual) | 斷言兩個(gè)相同類型的數(shù)組的元素一一對(duì)應(yīng)相等 |
assertArrayEquals(String message, T[] expected, T[] actual) | 斷言兩個(gè)相同類型的數(shù)組的元素一一對(duì)應(yīng)相等巷怜,如果不相等則拋出異常攜帶指定的message信息 |
fail() | 直接讓測(cè)試失敗 |
fail(String message) | 直接讓測(cè)試失敗并給出message錯(cuò)誤信息 |
assertThat(T actual, Matcher<? super T> matcher) | 斷言actual和matcher規(guī)則匹配 |
assertThat(String reason, T actual, Matcher<? super T> matcher) | 斷言actual和matcher規(guī)則匹配葛超,否則拋出異常攜帶指定的reason信息 |
其中assertEquals的方法,都對(duì)應(yīng)有一個(gè)assertNotEquals方法延塑,這里不列了绣张,assertThat是一個(gè)強(qiáng)大的方法:
Assert.assertThat(1, is(1));
Assert.assertThat(0, is(not(1)));
Assert.assertThat("hello", startsWith("h"));
List<String> items = new ArrayList<>();
items.add("aaa");
items.add("bbb");
Assert.assertThat(items, hasItem("aaa"));
需要靜態(tài)導(dǎo)入org.hamcrest.Matchers類里面的方法,更多匹配方法請(qǐng)參考這個(gè)類关带。
3. 運(yùn)行測(cè)試類
選中測(cè)試類右鍵Run運(yùn)行侥涵,控制面板中就會(huì)顯示測(cè)試結(jié)果:
如果所有的測(cè)試用例都正常返回了預(yù)期的結(jié)果,則面板中左側(cè)每個(gè)測(cè)試方法前面會(huì)帶一個(gè)綠色的對(duì)勾宋雏,否則方法前面會(huì)變成紅色感嘆號(hào)并且控制面板會(huì)輸出異常芜飘,現(xiàn)在來(lái)改一個(gè)業(yè)務(wù)方法試一下:
public boolean isTeenager(int age) {
if (age < 15) {
return false;
}
return false;
}
這里將age < 15改為輸出false,假設(shè)這是我們?cè)诰幋a的時(shí)候由于疏忽粗心造成的磨总,然后運(yùn)行測(cè)試類:
控制面板會(huì)告訴那一行出錯(cuò)了:
也就是說(shuō)這里沒(méi)有返回預(yù)期的結(jié)果嗦明,說(shuō)明我們編寫(xiě)的業(yè)務(wù)邏輯是有錯(cuò)誤的,這時(shí)就需要改bug了蚪燕。
4. 運(yùn)行單個(gè)測(cè)試方法或多個(gè)測(cè)試類
上面是運(yùn)行的整個(gè)測(cè)試類娶牌,如果要運(yùn)行測(cè)試類的單個(gè)方法,則鼠標(biāo)只選中某個(gè)要運(yùn)行的測(cè)試方法馆纳,然后右鍵選擇Run即可诗良。如果要同時(shí)運(yùn)行多個(gè)測(cè)試類,而如果多個(gè)測(cè)試類在同一個(gè)包下面鲁驶,則選中多個(gè)測(cè)試類所在的包目錄累榜,然后右鍵選擇Run運(yùn)行。否則可以通過(guò)下面的方式指定,創(chuàng)建一個(gè)空的測(cè)試類壹罚,然后添加注解:
@RunWith(Suite.class)
@Suite.SuiteClasses({SimpleClassTest.class, SimpleClass2Test.class})
public class RunMultiTest {
}
運(yùn)行這個(gè)測(cè)試類就可以將指定的測(cè)試類的方法一起運(yùn)行葛作。
二、Mockito測(cè)試框架的使用
前面介紹的只能測(cè)試不涉及Android相關(guān)Api的java代碼用例猖凛,如果涉及到Android相關(guān)Api的時(shí)候赂蠢,就不方便了,這時(shí)如果不依賴第三方庫(kù)的話可能需要使用儀器化測(cè)試跑到Android設(shè)備上去運(yùn)行辨泳,于是有一些比較好的第三方的替代框架可以來(lái)模擬使用Android的代碼測(cè)試虱岂,Mockito就是基于依賴注入實(shí)現(xiàn)的一個(gè)測(cè)試框架。
1. Mock概念的理解
什么是Mock, 這個(gè)單詞的中文意思就是“模仿”或者“虛假”的意思菠红,也就是要模仿一個(gè)對(duì)象第岖,為啥要模仿?
在傳統(tǒng)的JUnit單元測(cè)試中试溯,沒(méi)有消除在測(cè)試中對(duì)對(duì)象的依賴蔑滓,如A對(duì)象依賴B對(duì)象方法,在測(cè)試A對(duì)象的時(shí)候遇绞,我們需要構(gòu)造出B對(duì)象键袱,這樣子增加了測(cè)試的難度,或者使得我們對(duì)某些類的測(cè)試無(wú)法實(shí)現(xiàn)摹闽。這與單元測(cè)試的思路相違背蹄咖。
還有一個(gè)主要的問(wèn)題就是本地單元測(cè)試由于是運(yùn)行本地JVM環(huán)境,無(wú)法依賴Android的api付鹿,只靠純Junit的測(cè)試環(huán)境很難模擬出完整的Android環(huán)境澜汤,導(dǎo)致無(wú)法測(cè)試Android相關(guān)的代碼,而Mock就能解決這個(gè)問(wèn)題舵匾,通過(guò)Mock能夠很輕易的實(shí)現(xiàn)對(duì)象的模擬俊抵。
添加依賴:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'org.mockito:mockito-core:2.19.0'
....
}
- Mockito中幾種Mock對(duì)象的方式
使用之前通過(guò)靜態(tài)方式導(dǎo)入會(huì)使用更方便:
// 靜態(tài)導(dǎo)入會(huì)使代碼更簡(jiǎn)潔
import static org.mockito.Mockito.*;
直接mock一個(gè)對(duì)象:
@Test
public void testMock() {
SimpleClass mockSimple = Mockito.mock(SimpleClass.class);
assertNotNull(mockSimple);
}
注解方式mock一個(gè)對(duì)象:
@Mock
SimpleClass simple;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testMock() {
assertNotNull(simple);
}
運(yùn)行器方式mock一個(gè)對(duì)象:
@RunWith(MockitoJUnitRunner.class)
public class ExampleUnitTest {
@Mock
SimpleClass simple;
@Test
public void testMock() {
assertNotNull(simple);
}
}
MockitoRule方式mock一個(gè)對(duì)象:
public class ExampleUnitTest {
@Mock
SimpleClass simple;
@Rule //<--使用@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
@Test
public void testMock() {
assertNotNull(simple);
}
}
3. 驗(yàn)證行為
verify(T mock)函數(shù)的使用
verify(T mock)的作用是驗(yàn)證發(fā)生的某些行為等同于verify(mock, times(1)) 例如:
@Test
public void testMock() {
//創(chuàng)建mock對(duì)象
List mockedList = mock(List.class);
//使用mock對(duì)象
mockedList.add("one");
mockedList.clear();
//驗(yàn)證mockedList.add("one")是否被調(diào)用,如果被調(diào)用則當(dāng)前測(cè)試方法通過(guò)纽匙,否則失敗
verify(mockedList).add("one");
//驗(yàn)證 mockedList.clear()是否被調(diào)用务蝠,如果被調(diào)用則當(dāng)前測(cè)試方法通過(guò),否則失敗
verify(mockedList).clear();
}
@Test
public void testMock() {
mock.someMethod("some arg");
//驗(yàn)證mock.someMethod("some arg")是否被調(diào)用烛缔,如果被調(diào)用則測(cè)試方法通過(guò)馏段,否則失敗
verify(mock).someMethod("some arg");
}
也就是說(shuō)如果把調(diào)用的方法注釋掉,則運(yùn)行testMock()方法就會(huì)失敗践瓷。
通過(guò)verify關(guān)鍵字院喜,一旦mock對(duì)象被創(chuàng)建了,mock對(duì)象會(huì)記住所有的交互晕翠。然后你就可能選擇性的驗(yàn)證你感興趣的交互喷舀。
通常需要配合一些測(cè)試方法來(lái)驗(yàn)證某些行為砍濒,這些方法稱為"打樁方法"(Stub),打樁的意思是針對(duì)mock出來(lái)的對(duì)象進(jìn)行一些模擬操作硫麻,如設(shè)置模擬的返回值或拋出異常等爸邢。
常見(jiàn)的打樁方法:
方法名 | 方法含義 |
---|---|
doReturn(Object toBeReturned) | 提前設(shè)置要返回的值 |
doThrow(Throwable… toBeThrown) | 提前設(shè)置要拋出的異常 |
doAnswer(Answer answer) | 提前對(duì)結(jié)果進(jìn)行攔截 |
doCallRealMethod() | 調(diào)用某一個(gè)方法的真實(shí)實(shí)現(xiàn) |
doNothing() | 設(shè)置void函數(shù)什么也不做 |
thenReturn(T value) | 設(shè)置要返回的值 |
thenThrow(Throwable… throwables) | 設(shè)置要拋出的異常 |
thenAnswer(Answer<?> answer) | 對(duì)結(jié)果進(jìn)行攔截 |
例如:
@Test
public void testMock() {
// 你可以mock具體的類型,不僅只是接口
List mockedList = mock(List.class);
// 打測(cè)試樁
when(mockedList.get(0)).thenReturn("first");
doReturn("aaaa").when(mockedList).get(1);
when(mockedList.get(1)).thenThrow(new RuntimeException());
doThrow(new RuntimeException()).when(mockedList).clear();
// 輸出“first”
System.out.println(mockedList.get(0));
// 因?yàn)間et(999) 沒(méi)有打樁,因此輸出null, 注意模擬環(huán)境下這個(gè)地方是不會(huì)報(bào)IndexOutOfBoundsException異常的
System.out.println(mockedList.get(999));
// get(1)時(shí)會(huì)拋出異常
System.out.println(mockedList.get(1));
// clear會(huì)拋出異常
mockedList.clear();
}
doXXX和thenXXX使用上差不多拿愧,一個(gè)是調(diào)用方法之前設(shè)置好返回值杠河,一個(gè)是在調(diào)用方法之后設(shè)置返回值。默認(rèn)情況下浇辜,Mock出的對(duì)象的所有非void函數(shù)都有返回值券敌,對(duì)象類型的默認(rèn)返回的是null,例如返回int柳洋、boolean待诅、String的函數(shù),默認(rèn)返回值分別是0熊镣、false和null卑雁。
使用when(T methodCall)函數(shù)
打樁方法需要配合when(T methodCall)函數(shù),意思是使測(cè)試樁方法生效轧钓。當(dāng)你想讓這個(gè)mock能調(diào)用特定的方法返回特定的值序厉,那么你就可以使用它锐膜。
例如:
when(mock.someMethod()).thenReturn(10);
//你可以使用靈活的參數(shù)匹配毕箍,例如
when(mock.someMethod(anyString())).thenReturn(10);
//設(shè)置拋出的異常
when(mock.someMethod("some arg")).thenThrow(new RuntimeException());
//你可以對(duì)不同作用的連續(xù)回調(diào)的方法打測(cè)試樁:
//最后面的測(cè)試樁(例如:返回一個(gè)對(duì)象:"foo")決定了接下來(lái)的回調(diào)方法以及它的行為。
when(mock.someMethod("some arg"))
.thenReturn("foo")//第一次調(diào)用someMethod("some arg")會(huì)返回"foo"
.thenThrow(new RuntimeException());//第二次調(diào)用someMethod("some arg")會(huì)拋異常
//可以用以下方式替代比較小版本的連貫測(cè)試樁:
when(mock.someMethod("some arg"))
.thenReturn("one", "two");
//和下面的方式效果是一樣的
when(mock.someMethod("some arg"))
.thenReturn("one")
.thenReturn("two");
//比較小版本的連貫測(cè)試樁并且拋出異常:
when(mock.someMethod("some arg"))
.thenThrow(new RuntimeException(), new NullPointerException();
使用thenAnswer為回調(diào)做測(cè)試樁
when(mock.someMethod(anyString())).thenAnswer(new Answer() {
Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
return "called with arguments: " + args;
}
});
// 輸出 : "called with arguments: foo"
System.out.println(mock.someMethod("foo"));
使用doCallRealMethod()函數(shù)來(lái)調(diào)用某個(gè)方法的真實(shí)實(shí)現(xiàn)方法
注意道盏,在Mock環(huán)境下而柑,所有的對(duì)象都是模擬出來(lái)的,而方法的結(jié)果也是需要模擬出來(lái)的荷逞,如果你沒(méi)有為mock出的對(duì)象設(shè)置模擬結(jié)果媒咳,則會(huì)返回默認(rèn)值,例如:
public class Person {
public String getName() {
return "小明";
}
}
@Test
public void testPerson() {
Person mock = mock(Person.class);
//輸出null种远,除非設(shè)置發(fā)回模擬值when(mock.getName()).thenReturn("xxx");
System.out.println(mock.getName());
}
因?yàn)間etName()方法沒(méi)有設(shè)置模擬返回值涩澡,而getName()返回值是String類型的,因此直接調(diào)用的話會(huì)返回String的默認(rèn)值null坠敷,所以上面代碼如果要想輸出getName()方法的真實(shí)返回值的話妙同,需要設(shè)置doCallRealMethod():
@Test
public void testPerson() {
Person mock = mock(Person.class);
doCallRealMethod().when(mock).getName();
//輸出“小明”
System.out.println(mock.getName());
}
使用doNothing()函數(shù)是為了設(shè)置void函數(shù)什么也不做
需要注意的是默認(rèn)情況下返回值為void的函數(shù)在mocks中是什么也不做的但是,也會(huì)有一些特殊情況膝迎。如:
測(cè)試樁連續(xù)調(diào)用一個(gè)void函數(shù)時(shí):
doNothing().doThrow(new RuntimeException()).when(mock).someVoidMethod();
//does nothing the first time:
mock.someVoidMethod();
//throws RuntimeException the next time:
mock.someVoidMethod();
監(jiān)控真實(shí)的對(duì)象并且你想讓void函數(shù)什么也不做:
List list = new LinkedList();
List spy = spy(list);
//let's make clear() do nothing
doNothing().when(spy).clear();
spy.add("one");
//clear() does nothing, so the list still contains "one"
spy.clear();
使用doAnswer()函數(shù)測(cè)試void函數(shù)的回調(diào)
當(dāng)你想要測(cè)試一個(gè)無(wú)返回值的函數(shù)時(shí)粥帚,可以使用一個(gè)含有泛型類Answer參數(shù)的doAnswer()函數(shù)做回調(diào)測(cè)試。假設(shè)你有一個(gè)void方法有多個(gè)回調(diào)參數(shù)限次,當(dāng)你想指定執(zhí)行某個(gè)回調(diào)時(shí)芒涡,使用thenAnswer很難實(shí)現(xiàn)了,如果使用doAnswer()將非常簡(jiǎn)單,示例代碼如下:
MyCallback callback = mock(MyCallback.class);
Mockito.doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
//獲取第一個(gè)參數(shù)
MyCallback call = invocation.getArgument(0);
//指定回調(diào)執(zhí)行操作
call.onSuccess();
return null;
}
}).when(mockedObject.requset(callback));
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
System.out.println("onSuccess answer");
return null;
}
}).when(callback).onSuccess();
mockedObject.requset(callback)
需要使用doReturn函數(shù)代替thenReturn的情況
如當(dāng)監(jiān)控真實(shí)的對(duì)象并且調(diào)用真實(shí)的函數(shù)帶來(lái)的影響時(shí)
List list = new LinkedList();
List spy = spy(list);
//不可能完成的:真實(shí)方法被調(diào)用的時(shí)候list仍是空的费尽,所以spy.get(0)會(huì)拋出IndexOutOfBoundsException()異常
when(spy.get(0)).thenReturn("foo");
//這時(shí)你應(yīng)該使用doReturn()函數(shù)
doReturn("foo").when(spy).get(0);
使用doThrow()函數(shù)來(lái)測(cè)試void函數(shù)拋出異常
SimpleClass mock = mock(SimpleClass.class);
doThrow(new RuntimeException()).when(mock).someVoidMethod();
mock.someVoidMethod();
總之使用doThrow(), doAnswer(), doNothing(), doReturn() and doCallRealMethod() 這些函數(shù)時(shí)可以在適當(dāng)?shù)那闆r下調(diào)用when()來(lái)解決一些問(wèn)題.赠群, 如當(dāng)你需要下面這些功能時(shí)這是必須的:
- 測(cè)試void函數(shù)
- 在受監(jiān)控的對(duì)象上測(cè)試函數(shù)
- 不只一次的測(cè)試同一個(gè)函數(shù),在測(cè)試過(guò)程中改變mock對(duì)象的行為
4. 驗(yàn)證方法的調(diào)用次數(shù)
需要配合使用一些方法
方法 | 含義 |
---|---|
times(int wantedNumberOfInvocations) | 驗(yàn)證調(diào)用方法的次數(shù) |
never() | 驗(yàn)證交互沒(méi)有發(fā)生,相當(dāng)于times(0) |
only() | 驗(yàn)證方法只被調(diào)用一次旱幼,相當(dāng)于times(1) |
atLeast(int minNumberOfInvocations) | 至少進(jìn)行n次驗(yàn)證 |
atMost(int maxNumberOfInvocations) | 至多進(jìn)行n次驗(yàn)證 |
after(long millis) | 在給定的時(shí)間后進(jìn)行驗(yàn)證 |
timeout(long millis) | 驗(yàn)證方法執(zhí)行是否超時(shí) |
description(String description) | 驗(yàn)證失敗時(shí)輸出的內(nèi)容 |
verifyZeroInteractions | 驗(yàn)證mock對(duì)象沒(méi)有交互 |
例如:
mock.someMethod("some arg");
mock.someMethod("some arg");
//驗(yàn)證mock.someMethod("some arg")被連續(xù)調(diào)用兩次乎串,即如果沒(méi)有調(diào)用兩次則驗(yàn)證失敗
verify(mock, times(2)).someMethod("some arg");
//注意,下面三種是等價(jià)的速警,都是驗(yàn)證someMethod()被只調(diào)用一次
verify(mock).someMethod("some arg");
verify(mock, times(1)).someMethod("some arg");
verify(mock, only()).someMethod("some arg");
mPerson.getAge();
mPerson.getAge();
//驗(yàn)證至少調(diào)用2次
verify(mPerson, atLeast(2)).getAge();
//驗(yàn)證至多調(diào)用2次
verify(mPerson, atMost(2)).getAge();
//下面兩種等價(jià)叹誉,驗(yàn)證調(diào)用次數(shù)為0
verify(mPerson, never()).getAge();
verify(mPerson, times(0)).getAge();
mPerson.getAge();
mPerson.getAge();
long current = System.currentTimeMillis();
System.out.println(current );
//延時(shí)1s后驗(yàn)證mPerson.getAge()是否被執(zhí)行了2次
verify(mPerson, after(1000).times(2)).getAge();
System.out.println(System.currentTimeMillis() - current);
mPerson.getAge();
mPerson.getAge();
//驗(yàn)證方法在100ms超時(shí)前被調(diào)用2次
verify(mPerson, timeout(100).times(2)).getAge();
@Test
public void testVerifyZeroInteractions() {
Person person = mock(Person.class);
person.eat("a");
//由于person對(duì)象發(fā)生了交互,所以這里驗(yàn)證失敗闷旧,把上面的調(diào)用注釋掉這里就會(huì)驗(yàn)證成功
verifyZeroInteractions(person);
//可以驗(yàn)證多個(gè)對(duì)象沒(méi)有交互
//verifyZeroInteractions(person长豁,person2 );
}
@Test
public void testVerifyZeroInteractions() {
Person person = mock(Person.class);
person.eat("a");
verify(person).eat("a");
//注意,這將會(huì)無(wú)法到達(dá)驗(yàn)證目的忙灼,不能跟verify()混用
verifyZeroInteractions(person匠襟,person2 );
}
5. 參數(shù)匹配器 (matchers)
Mockito以自然的java風(fēng)格來(lái)驗(yàn)證參數(shù)值: 使用equals()函數(shù)。有時(shí)该园,當(dāng)需要額外的靈活性時(shí)你可能需要使用參數(shù)匹配器酸舍,也就是argument matchers :
// 使用內(nèi)置的anyInt()參數(shù)匹配器
when(mockedList.get(anyInt())).thenReturn("element");
// 使用自定義的參數(shù)匹配器( 在isValid()函數(shù)中返回你自己的匹配器實(shí)現(xiàn) )
when(mockedList.contains(argThat(isValid()))).thenReturn("element");
// 輸出element
System.out.println(mockedList.get(999));
// 你也可以驗(yàn)證參數(shù)匹配器
verify(mockedList).get(anyInt());
常用的參數(shù)匹配器:
方法名 | 含義 |
---|---|
anyObject() | 匹配任何對(duì)象 |
any(Class type) | 與anyObject()一樣 |
any() | 與anyObject()一樣 |
anyBoolean() | 匹配任何boolean和非空Boolean |
anyByte() | 匹配任何byte和非空Byte |
anyCollection() | 匹配任何非空Collection |
anyDouble() | 匹配任何double和非空Double |
anyFloat() | 匹配任何float和非空Float |
anyInt() | 匹配任何int和非空Integer |
anyList() | 匹配任何非空List |
anyLong() | 匹配任何long和非空Long |
anyMap() | 匹配任何非空Map |
anyString() | 匹配任何非空String |
contains(String substring) | 參數(shù)包含給定的substring字符串 |
argThat(ArgumentMatcher matcher) | 創(chuàng)建自定義的參數(shù)匹配模式 |
eq(T value) | 匹配參數(shù)等于某個(gè)值 |
一些示例代碼:
@Test
public void testPersonAny(){
when(mPerson.eat(any(String.class))).thenReturn("米飯");
//或:
when(mPerson.eat(anyString())).thenReturn("米飯");
//輸出米飯
System.out.println(mPerson.eat("面條"));
}
@Test
public void testPersonContains(){
when(mPerson.eat(contains("面"))).thenReturn("面條");
//輸出面條
System.out.println(mPerson.eat("面"));
}
@Test
public void testPersonArgThat(){
//自定義輸入字符長(zhǎng)度為偶數(shù)時(shí),輸出面條里初。
when(mPerson.eat(argThat(new ArgumentMatcher<String>() {
@Override
public boolean matches(String argument) {
return argument.length() % 2 == 0;
}
}))).thenReturn("面條");
//輸出面條
System.out.println(mPerson.eat("1234"));
}
需要注意的是啃勉,如果你打算使用參數(shù)匹配器,那么所有參數(shù)都必須由匹配器提供双妨。例如:
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
// 上述代碼是正確的,因?yàn)閑q()也是一個(gè)參數(shù)匹配器
verify(mock).someMethod(anyInt(), anyString(), "third argument");
// 上述代碼是錯(cuò)誤的, 因?yàn)樗袇?shù)必須由匹配器提供淮阐,而參數(shù)"third argument"并非由參數(shù)匹配器提供,因此會(huì)拋出異常
像anyObject(), eq()這樣的匹配器函數(shù)不會(huì)返回匹配器刁品。它們會(huì)在內(nèi)部將匹配器記錄到一個(gè)棧當(dāng)中泣特,并且返回一個(gè)假的值,通常為null挑随。
6. 使用InOrder驗(yàn)證執(zhí)行執(zhí)行順序
驗(yàn)證執(zhí)行執(zhí)行順序主要使用InOrder函數(shù)
如状您,驗(yàn)證mock一個(gè)對(duì)象的函數(shù)執(zhí)行順序:
@Test
public void testInorder() {
List<String> singleMock = mock(List.class);
singleMock.add("小明");
singleMock.add("小紅");
// 為該mock對(duì)象創(chuàng)建一個(gè)inOrder對(duì)象
InOrder inOrder = inOrder(singleMock);
// 驗(yàn)證add函數(shù)首先執(zhí)行的是add("小明"),然后才是add("小紅"),否則測(cè)試失敗
inOrder.verify(singleMock).add("小明");
inOrder.verify(singleMock).add("小紅");
}
驗(yàn)證多個(gè)mock對(duì)象的函數(shù)執(zhí)行順序:
@Test
public void testInorderMulti() {
List<String> firstMock = mock(List.class);
List<String> secondMock = mock(List.class);
firstMock.add("小明");
secondMock.add("小紅");
// 為這兩個(gè)Mock對(duì)象創(chuàng)建inOrder對(duì)象
InOrder inOrder = inOrder(firstMock, secondMock);
// 驗(yàn)證它們的執(zhí)行順序
inOrder.verify(firstMock).add("小明");
inOrder.verify(secondMock).add("小紅");
}
驗(yàn)證執(zhí)行順序是非常靈活的兜挨,你不需要一個(gè)一個(gè)的驗(yàn)證所有交互膏孟,只需要驗(yàn)證你感興趣的對(duì)象即可。 你可以選擇單個(gè)mock對(duì)象和多個(gè)mock對(duì)象混合著來(lái)暑劝,也可以僅通過(guò)那些需要驗(yàn)證順序的mock對(duì)象來(lái)創(chuàng)建InOrder對(duì)象骆莹。
7. 使用Spy監(jiān)控真實(shí)對(duì)象
監(jiān)控真實(shí)對(duì)象使用spy()函數(shù)生成,或者也可以像@Mock那樣使用@Spy注解來(lái)生成一個(gè)監(jiān)控對(duì)象担猛, 當(dāng)你你為真實(shí)對(duì)象創(chuàng)建一個(gè)監(jiān)控(spy)對(duì)象后幕垦,在你使用這個(gè)spy對(duì)象時(shí)真實(shí)的對(duì)象也會(huì)也調(diào)用丢氢,除非它的函數(shù)被stub了。盡量少使用spy對(duì)象先改,使用時(shí)也需要小心形式疚察。
@Test
public void testSpy() {
List<String> list = new ArrayList<>();
List<String> spy = spy(list);
// 你可以選擇為某些函數(shù)打樁
when(spy.size()).thenReturn(100);
// 調(diào)用真實(shí)對(duì)象的函數(shù)
spy.add("one");
spy.add("two");
// 輸出第一個(gè)元素"one"
System.out.println(spy.get(0));
// 因?yàn)閟ize()函數(shù)被打樁了,因此這里返回的是100
System.out.println(spy.size());
// 驗(yàn)證交互
verify(spy).add("one");
verify(spy).add("two");
}
使用@Spy生成監(jiān)控對(duì)象:
@Spy
Person mSpyPerson;
@Test
public void testSpyPerson() {
//將會(huì)輸出Person 類中g(shù)etName()的真實(shí)實(shí)現(xiàn),而不是null
System.out.println(mSpyPerson.getName());
}
理解監(jiān)控真實(shí)對(duì)象非常重要!有時(shí),在監(jiān)控對(duì)象上使用when(Object)來(lái)進(jìn)行打樁是不可能或者不切實(shí)際的舆床。因此,當(dāng)使用監(jiān)控對(duì)象時(shí)請(qǐng)考慮doReturn|Answer|Throw()函數(shù)族來(lái)進(jìn)行打樁岛抄。例如:
List list = new LinkedList();
List spy = spy(list);
// 不可能實(shí)現(xiàn) : 因?yàn)楫?dāng)調(diào)用spy.get(0)時(shí)會(huì)調(diào)用真實(shí)對(duì)象的get(0)函數(shù),
// 此時(shí)會(huì)發(fā)生IndexOutOfBoundsException異常,因?yàn)檎鎸?shí)List對(duì)象是空的
when(spy.get(0)).thenReturn("foo");
// 你需要使用doReturn()來(lái)打樁
doReturn("foo").when(spy).get(0);
8. 使用ArgumentCaptor進(jìn)行參數(shù)捕獲
參數(shù)捕獲主要為了下一步的斷言做準(zhǔn)備狈茉,示例代碼:
@Test
public void argumentCaptorTest() {
List<Object> mock = mock(List.class);
mock.add("John");
//構(gòu)建要捕獲的參數(shù)類型夫椭,這里是String
ArgumentCaptor argument = ArgumentCaptor.forClass(String.class);
//在verify方法的參數(shù)中調(diào)用argument.capture()方法來(lái)捕獲輸入的參數(shù)
verify(mock).add(argument.capture());
//驗(yàn)證“John”參數(shù)捕獲
assertEquals("John", argument.getValue());
}
@Test
public void argumentCaptorTest2() {
List<Object> mock = mock(List.class);
mock.add("Brian");
mock.add("Jim");
ArgumentCaptor argument = ArgumentCaptor.forClass(String.class);
verify(mock, times(2)).add(argument.capture());
//如果又多次參數(shù)調(diào)用,argument.getValue()捕獲到的是最后一次調(diào)用的參數(shù)
assertEquals("Jim", argument.getValue());
//如果要獲取所有的參數(shù)值可以調(diào)用argument.getAllValues()
assertArrayEquals(new Object[]{"Brian","Jim"}, argument.getAllValues().toArray());
}
9. 使用@InjectMocks自動(dòng)注入依賴對(duì)象
有時(shí)我們要測(cè)試的對(duì)象內(nèi)部需要依賴另一個(gè)對(duì)象氯庆,例如:
public class User {
private Address address;
public void setAddress(Address address) {
this.address = address;
}
public String getAddress() {
return address.getDetail();
}
}
public class Address {
public String getDetail() {
return "detail Address";
}
}
User類內(nèi)部需要依賴Address類蹭秋,當(dāng)我們測(cè)試的時(shí)候需要mock出這兩個(gè)對(duì)象,然后將Address對(duì)象傳入到User當(dāng)中堤撵,這樣如果依賴的對(duì)象多了的話就相當(dāng)麻煩仁讨,Mockito 提供了可以不用去手動(dòng)注入對(duì)象的方法,首先使用@InjectMocks注解需要被注入的對(duì)象实昨,如User洞豁,然后需要被依賴注入的對(duì)象使用@Mock或@Spy注解,之后Mockito 會(huì)自動(dòng)完成注入過(guò)程屠橄,例如:
@InjectMocks
User mTestUser;
@Mock
Address mAddress;
@Test
public void argumentInjectMock() {
when(mAddress.getDetail()).thenReturn("浙江杭州");
System.out.println(mTestUser.getAddress());
}
這樣就不用關(guān)心為User 設(shè)置Address 族跛,只要為User需要依賴的類添加注解就可以了闰挡,然后直接將重點(diǎn)放到測(cè)試方法的編寫(xiě)上锐墙。
或者使用@Spy監(jiān)控真實(shí)對(duì)象注入也可以:
@InjectMocks
User mTestUser;
@Spy
Address mAddress;
@Test
public void argumentInjectMock() {
// when(mAddress.getDetail()).thenReturn("浙江杭州");
System.out.println(mTestUser.getAddress());
}
其他:
連續(xù)調(diào)用的另一種更簡(jiǎn)短的版本:
// 第一次調(diào)用時(shí)返回"one",第二次返回"two",第三次返回"three"
when(mock.someMethod("some arg")).thenReturn("one", "two", "three");
參考:Mockito 中文文檔
三、PowerMockito框架使用
Mockito框架基本滿足需求但是有一些局限性长酗,如對(duì)static溪北、final、private等方法不能mock夺脾,PowerMockito就可以解決這些問(wèn)題之拨,PowerMockito是一個(gè)擴(kuò)展了其它如EasyMock等mock框架的、功能更加強(qiáng)大的框架咧叭。PowerMock使用一個(gè)自定義類加載器和字節(jié)碼操作來(lái)模擬靜態(tài)方法蚀乔,構(gòu)造函數(shù),final類和方法菲茬,私有方法吉挣,去除靜態(tài)初始化器等等派撕。
添加依賴:
testImplementation 'org.powermock:powermock-module-junit4:2.0.2'
testImplementation 'org.powermock:powermock-module-junit4-rule:2.0.2'
testImplementation 'org.powermock:powermock-api-mockito2:2.0.2'
testImplementation 'org.powermock:powermock-classloading-xstream:2.0.2'
1. 普通Mock的方式
目標(biāo)類:
public class CommonExample {
public boolean callArgumentInstance(File file) {
return file.exists();
}
}
測(cè)試類:
public class CommonExamplePowerMockTest {
@Test
public void testCallArgumentInstance() {
File file = PowerMockito.mock(File.class);
CommonExample commonExample = new CommonExample();
PowerMockito.when(file.exists()).thenReturn(true);
Assert.assertTrue(commonExample.callArgumentInstance(file));
}
}
普通Mock方式是外部傳遞Mock參數(shù),基本上和單獨(dú)使用Mockito是一樣的睬魂,使用純Mockito的api也可以完成這個(gè)測(cè)試终吼。
2. Mock方法內(nèi)部new出來(lái)的對(duì)象
public class CommonExample {
public boolean callArgumentInstance(String path) {
File file = new File(path);
return file.exists();
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(CommonExample.class)
public class CommonExamplePowerMockTest {
@Test
public void callCallArgumentInstance2() throws Exception {
File file = PowerMockito.mock(File.class);
CommonExample commonExample = new CommonExample();
PowerMockito.whenNew(File.class).withArguments("aaa").thenReturn(file);
PowerMockito.when(file.exists()).thenReturn(true);
Assert.assertTrue(commonExample.callArgumentInstance("aaa"));
}
}
跟前面有一點(diǎn)區(qū)別的就是,這里要測(cè)試的方法內(nèi)部創(chuàng)建了File對(duì)象氯哮,這時(shí)需要通過(guò)PowerMockito.whenNew(File.class).withArguments("aaa").thenReturn(file)方法模擬創(chuàng)建File的操作际跪,當(dāng)File類以aaa的參數(shù)創(chuàng)建的時(shí)候返回已經(jīng)mock出來(lái)的file對(duì)象。同時(shí)這時(shí)需要在測(cè)試類上添加注解@RunWith(PowerMockRunner.class)和@PrepareForTest(CommonExample.class)喉钢,注意是在類上面添加姆打,不是在方法上,一開(kāi)始在方法上添加時(shí)提示找不到測(cè)試方法肠虽,@PrepareForTest()括號(hào)里面指定的是要測(cè)試的目標(biāo)類穴肘。
3. Mock普通對(duì)象的final方法
public class CommonExample {
public boolean callFinalMethod(DependencyClass dependency) {
return dependency.isValidate();
}
}
public class DependencyClass {
public final boolean isValidate() {
// do something
return false;
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({CommonExample.class, DependencyClass.class})
public class CommonExamplePowerMockTest {
@Test
public void callFinalMethod() {
DependencyClass dependency = PowerMockito.mock(DependencyClass.class);
CommonExample commonExample = new CommonExample();
PowerMockito.when(dependency.isValidate()).thenReturn(true);
Assert.assertTrue(commonExample.callFinalMethod(dependency));
}
}
同樣這里mock出來(lái)需要依賴的類的對(duì)象,然后傳遞給調(diào)用方法舔痕,這里同樣需要添加@RunWith和@PrepareForTest评抚,@PrepareForTest可以指定多個(gè)目標(biāo)類,但是這里如果你只需要測(cè)試final的話伯复,只添加DependencyClass.class一個(gè)就可以了慨代。
4. Mock普通類的靜態(tài)方法
public final class Utils {
public static String getUUId() {
return UUID.randomUUID().toString();
}
}
public class CommonExample {
public String printUUID() {
return Utils.getUUId();
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(Utils.class)
public class StaticUnitTest {
@Before
public void setUp() throws Exception {
PowerMockito.mockStatic(Utils.class);
}
@Test
public void getUUId() {
PowerMockito.when(Utils.getUUId()).thenReturn("FAKE UUID");
CommonExample commonExample = new CommonExample();
assertThat(commonExample.printUUID(), is("FAKE UUID"));
}
}
同樣需要指定@RunWith和@PrepareForTest,@PrepareForTest中指定靜態(tài)方法所在的類啸如,測(cè)試靜態(tài)方法之前需要調(diào)用PowerMockito.mockStatic()方法來(lái)mock靜態(tài)類侍匙,然后就通過(guò)when().thenReturn()方法指定靜態(tài)方法的模擬返回值即可。
5. verify靜態(tài)方法的調(diào)用次數(shù)
@Test
public void testVerify() {
PowerMockito.when(Utils.getUUId()).thenReturn("FAKE UUID");
CommonExample commonExample = new CommonExample();
System.out.println(commonExample.printUUID());
PowerMockito.verifyStatic(Utils.class);
Utils.getUUId();
}
靜態(tài)方法通過(guò)PowerMockito.verifyStatic(Class c)進(jìn)行驗(yàn)證叮雳,不過(guò)這里跟Mocktio有一點(diǎn)區(qū)別的是需要在這個(gè)方法的后面再調(diào)用一次靜態(tài)方法想暗,否則不行。這里PowerMockito.verifyStatic(Utils.class)其實(shí)等同于PowerMockito.verifyStatic(Utils.class, times(1))帘不,如果想驗(yàn)證超過(guò)一次的说莫,那么:
@Test
public void testVerify() {
PowerMockito.when(Utils.getUUId()).thenReturn("FAKE UUID");
CommonExample commonExample = new CommonExample();
System.out.println(commonExample.printUUID());
System.out.println(commonExample.printUUID());
PowerMockito.verifyStatic(Utils.class, Mockito.times(2));
Utils.getUUId();
}
這時(shí)PowerMockito.verifyStatic()第一個(gè)參數(shù)指定靜態(tài)方法類的Class,第二個(gè)參數(shù)接收一個(gè)VerificationMode類型的參數(shù)寞焙,因此傳遞Mockito中的任何驗(yàn)證方法次數(shù)的函數(shù)都可以储狭,Mockito中的驗(yàn)證函數(shù)會(huì)返回的是一個(gè)VerificationMode類型。同樣在PowerMockito.verifyStatic方法后面要調(diào)用一次要驗(yàn)證的靜態(tài)方法捣郊,總感覺(jué)這里很奇怪辽狈。。呛牲。
6. 使用真實(shí)返回值
如果在測(cè)試的過(guò)程中又遇到不需要mock出來(lái)的靜態(tài)方法的模擬返回值刮萌,而是需要真實(shí)的返回值,怎么辦呢娘扩,其實(shí)跟Mockito一樣着茸,PowerMockito同樣提供thenCallRealMethod或者doCallRealMethod方法:
@Test
public void testRealCall() throws Exception {
PowerMockito.when(Utils.getUUId()).thenReturn("FAKE UUID");
//...
PowerMockito.when(Utils.getUUId()).thenCallRealMethod();
//與下面等價(jià)
//PowerMockito.doCallRealMethod().when(Utils.class, "getUUId");
System.out.println(Utils.getUUId());
}
或者直接使用spy監(jiān)控真實(shí)對(duì)象也可以:
@Test
public void testRealCall() {
PowerMockito.spy(Utils.class);
System.out.println(Utils.getUUId());
}
7. Mock私有方法
public class CommonExample {
public boolean callPrivateMethod() {
return isExist();
}
private boolean isExist() {
return false;
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(CommonExample.class)
public class PrivateUnitTest {
@Test
public void testCallPrivateMethod() throws Exception {
CommonExample commonExample = PowerMockito.mock(CommonExample.class);
PowerMockito.when(commonExample.callPrivateMethod()).thenCallRealMethod();
PowerMockito.when(commonExample, "isExist").thenReturn(true);
Assert.assertTrue(commonExample.callPrivateMethod());
}
}
在使用上跟純Mockito的沒(méi)有太大區(qū)別僧凤,只不過(guò)Mock私有方法是通過(guò)下面的api實(shí)現(xiàn)的:
PowerMockito.when(Object instance, String methodName, Object... arguments)
在PowerMockito中when函數(shù)與Mockito相比,最大的變化就是多了一些傳遞String類型的methodName的重載方法元扔,這樣在使用上幾乎無(wú)所不能了躯保。
8. Mock普通類的私有變量
public class CommonExample {
private static final int STATE_NOT_READY = 0;
private static final int STATE_READY = 1;
private int mState = STATE_NOT_READY;
public boolean doSomethingIfStateReady() {
if (mState == STATE_READY) {
// DO some thing
return true;
} else {
return false;
}
}
}
@Test
public void testDoSomethingIfStateReady() throws Exception {
CommonExample sample = new CommonExample();
Whitebox.setInternalState(sample, "mState", 1);
assertThat(sample.doSomethingIfStateReady(), is(true));
}
通過(guò)Whitebox.setInternalState來(lái)改變私有成員變量,這種情況下不需要指定@RunWith和@PrepareForTest澎语。
9. 對(duì)靜態(tài)void方法進(jìn)行Mock
public class CommonExample {
public static void doSomething(String a) {
System.out.println("doSomething"+a);
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({CommonExample.class})
public class StaticUnitTest {
@Test
public void testStaticVoid() throws Exception {
PowerMockito.mockStatic(CommonExample.class);
PowerMockito.doNothing().when(CommonExample.class, "doSomething", Mockito.any());
CommonExample.doSomething("aaa");
}
}
默認(rèn)情況下通過(guò)PowerMockito.mockStatic的靜態(tài)類的void的方法是什么也不做的途事,但是可以顯示的執(zhí)行doNothing, 上面的代碼將doNothing那行注釋掉也是什么也不做的。那如果想做一些事而不是doNothing呢擅羞,跟Mockito一樣尸变,采用doAnswer:
@Test
public void testStaticVoid() throws Exception {
PowerMockito.mockStatic(CommonExample.class);
PowerMockito.doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
System.out.println(invocation.getArguments()[0]);
return null;
}
}).when(CommonExample.class, "doSomething", Mockito.any());
CommonExample.doSomething("aaa");
}
10. Mock系統(tǒng)的final靜態(tài)類
public class CommonExample {
public int callSystemStaticMethod(int a, int b) {
return Math.max(a, a+b);
}
}
@RunWith(PowerMockRunner.class)
@PrepareForTest(CommonExample.class)
public class StaticUnitTest {
@Test
public void callSystemStaticMethod() {
CommonExample commonExample = new CommonExample();
PowerMockito.mockStatic(Math.class);
PowerMockito.when(Math.max(anyInt(), anyInt())).thenReturn(100);
Assert.assertEquals(100, commonExample.callSystemStaticMethod(10, -5));
}
}
@PrepareForTest中添加調(diào)用系統(tǒng)類所在的類,這里需要注意的是如果你使用PowerMockito來(lái)mock系統(tǒng)靜態(tài)final類减俏,則gradle依賴中不能再添加單純Mockito的依賴庫(kù)召烂,否則這里將不能mock成功,會(huì)提示Mockito can not mock/spy final class, 因?yàn)镻owerMockito本身已經(jīng)有對(duì)Mockito的依賴庫(kù)支持了娃承,所以只依賴PowerMockito就可以了奏夫。除了系統(tǒng)靜態(tài)final類的情況,其他的情況下PowerMockito和Mockito可以同時(shí)依賴(我測(cè)試是沒(méi)有問(wèn)題的)历筝。另外單純的Mockito新版本中也支持對(duì) final 類 final 方法的 Mock酗昼,但是需要添加配置文件并不友好。
四梳猪、Robolectric測(cè)試框架的使用
由于Robolectric部分的內(nèi)容比較長(zhǎng)麻削,所以單獨(dú)放了一篇文章中:Android單元測(cè)試框架Robolectric的學(xué)習(xí)使用
五、Espresso測(cè)試框架的使用
Espresso是用于Android儀器化測(cè)試的測(cè)試框架春弥,是谷歌官方主推的一個(gè)測(cè)試庫(kù)呛哟。由于Espresso部分的內(nèi)容也比較長(zhǎng),所以單獨(dú)放了一篇文章中:Espresso測(cè)試框架的使用