Android单元测试学习总结
文章目錄
- 一、本地單元測(cè)試
- 1. 創(chuàng)建測(cè)試類
- 2. Assert類中的常用斷言方法
- 3. 運(yùn)行測(cè)試類
- 4. 運(yùn)行單個(gè)測(cè)試方法或多個(gè)測(cè)試類
- 二、Mockito測(cè)試框架的使用
- 1. Mock概念的理解
- 2. Mockito中幾種Mock對(duì)象的方式
- 3. 驗(yàn)證行為
- verify(T mock)函數(shù)的使用
- 使用`when(T methodCall)`函數(shù)
- 使用`thenAnswer`為回調(diào)做測(cè)試樁
- 使用`doCallRealMethod()`函數(shù)來調(diào)用某個(gè)方法的真實(shí)實(shí)現(xiàn)方法
- 使用`doNothing()`函數(shù)是為了設(shè)置void函數(shù)什么也不做
- 使用`doAnswer()`函數(shù)測(cè)試void函數(shù)的回調(diào)
- 需要使用doReturn函數(shù)代替thenReturn的情況
- 使用`doThrow()`函數(shù)來測(cè)試void函數(shù)拋出異常
- 4. 驗(yàn)證方法的調(diào)用次數(shù)
- 5. 參數(shù)匹配器 (matchers)
- 6. 使用InOrder驗(yàn)證執(zhí)行執(zhí)行順序
- 7. 使用Spy監(jiān)控真實(shí)對(duì)象
- 8. 使用ArgumentCaptor進(jìn)行參數(shù)捕獲
- 9. 使用@InjectMocks自動(dòng)注入依賴對(duì)象
- 三、PowerMockito框架使用
- 1. 普通Mock的方式
- 2. Mock方法內(nèi)部new出來的對(duì)象
- 3. Mock普通對(duì)象的final方法
- 4. Mock普通類的靜態(tài)方法
- 5. verify靜態(tài)方法的調(diào)用次數(shù)
- 6. 使用真實(shí)返回值
- 7. Mock私有方法
- 8. Mock普通類的私有變量
- 9. 對(duì)靜態(tài)void方法進(jìn)行Mock
- 10. Mock系統(tǒng)的final靜態(tài)類
- 四、Robolectric測(cè)試框架的使用
- 五、Espresso測(cè)試框架的使用
Android單元測(cè)試主要分為以下兩種
- 本地單元測(cè)試(Junit Test), 本地單元測(cè)試是純java代碼的測(cè)試,只運(yùn)行在本地電腦的JVM環(huán)境上,不依賴于Android框架的任何api, 因此執(zhí)行速度快,效率較高,但是無法測(cè)試Android相關(guān)的代碼。
- 儀器化測(cè)試(Android Test),是針對(duì)Android相關(guān)代碼的測(cè)試,需要運(yùn)行在真機(jī)設(shè)備或模擬器上,運(yùn)行速度較慢,但是可以測(cè)試UI的交互以及對(duì)設(shè)備信息的訪問,得到接近真實(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ù)(如: 斷開數(shù)據(jù)連接) |
| @Test (expected = Exception.class) | 如果該測(cè)試方法沒有拋出Annotation中的Exception類型(子類也可以),則測(cè)試失敗 |
| @Test(timeout=100) | 如果該測(cè)試方法耗時(shí)超過100毫秒,則測(cè)試失敗,用于性能測(cè)試 |
| @Ignore 或者 @Ignore(“太耗時(shí)”) | 忽略當(dāng)前測(cè)試方法,一般用于測(cè)試方法還沒有準(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è)試類
接下來就可以創(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è)試方法
接下來編寫測(cè)試方法,首先在要測(cè)試的目標(biāo)類中寫幾個(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;@Beforepublic void setUp() throws Exception {simpleClass = new SimpleClass();}@Afterpublic void tearDown() throws Exception {}@Testpublic void isTeenager() {Assert.assertFalse(simpleClass.isTeenager(20));Assert.assertTrue(simpleClass.isTeenager(14));}@Testpublic void add() {Assert.assertEquals(simpleClass.add(3, 2), 5);Assert.assertNotEquals(simpleClass.add(3, 2), 4);}@Testpublic 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è)試方法中編寫測(cè)試用例,這里使用org.junit.Assert包中的斷言方法,有很多assertXXX方法,可以自己選擇用來判斷目標(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)在來改一個(gè)業(yè)務(wù)方法試一下:
這里將age < 15改為輸出false,假設(shè)這是我們?cè)诰幋a的時(shí)候由于疏忽粗心造成的,然后運(yùn)行測(cè)試類:
控制面板會(huì)告訴那一行出錯(cuò)了:
也就是說這里沒有返回預(yù)期的結(jié)果,說明我們編寫的業(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)行。否則可以通過下面的方式指定,創(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)行,于是有一些比較好的第三方的替代框架可以來模擬使用Android的代碼測(cè)試,Mockito就是基于依賴注入實(shí)現(xiàn)的一個(gè)測(cè)試框架。
1. Mock概念的理解
什么是Mock, 這個(gè)單詞的中文意思就是“模仿”或者“虛假”的意思,也就是要模仿一個(gè)對(duì)象,為啥要模仿?
在傳統(tǒng)的JUnit單元測(cè)試中,沒有消除在測(cè)試中對(duì)對(duì)象的依賴,如A對(duì)象依賴B對(duì)象方法,在測(cè)試A對(duì)象的時(shí)候,我們需要構(gòu)造出B對(duì)象,這樣子增加了測(cè)試的難度,或者使得我們對(duì)某些類的測(cè)試無法實(shí)現(xiàn)。這與單元測(cè)試的思路相違背。
還有一個(gè)主要的問題就是本地單元測(cè)試由于是運(yùn)行本地JVM環(huán)境,無法依賴Android的api,只靠純Junit的測(cè)試環(huán)境很難模擬出完整的Android環(huán)境,導(dǎo)致無法測(cè)試Android相關(guān)的代碼,而Mock就能解決這個(gè)問題,通過Mock能夠很輕易的實(shí)現(xiàn)對(duì)象的模擬。
添加依賴:
dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])testImplementation 'org.mockito:mockito-core:2.19.0'.... }2. Mockito中幾種Mock對(duì)象的方式
使用之前通過靜態(tài)方式導(dǎo)入會(huì)使用更方便:
// 靜態(tài)導(dǎo)入會(huì)使代碼更簡(jiǎn)潔import static org.mockito.Mockito.*;直接mock一個(gè)對(duì)象:
@Testpublic void testMock() {SimpleClass mockSimple = Mockito.mock(SimpleClass.class);assertNotNull(mockSimple);}注解方式mock一個(gè)對(duì)象:
@MockSimpleClass simple;@Beforepublic void setUp() {MockitoAnnotations.initMocks(this);}@Testpublic void testMock() {assertNotNull(simple);}運(yùn)行器方式mock一個(gè)對(duì)象:
@RunWith(MockitoJUnitRunner.class) public class ExampleUnitTest {@MockSimpleClass simple;@Testpublic void testMock() {assertNotNull(simple);} }MockitoRule方式mock一個(gè)對(duì)象:
public class ExampleUnitTest {@MockSimpleClass simple;@Rule //<--使用@Rulepublic MockitoRule mockitoRule = MockitoJUnit.rule();@Testpublic 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è)試方法通過,否則失敗verify(mockedList).add("one");//驗(yàn)證 mockedList.clear()是否被調(diào)用,如果被調(diào)用則當(dāng)前測(cè)試方法通過,否則失敗verify(mockedList).clear();} @Test public void testMock() {mock.someMethod("some arg");//驗(yàn)證mock.someMethod("some arg")是否被調(diào)用,如果被調(diào)用則測(cè)試方法通過,否則失敗verify(mock).someMethod("some arg");}也就是說如果把調(diào)用的方法注釋掉,則運(yùn)行testMock()方法就會(huì)失敗。
通過verify關(guān)鍵字,一旦mock對(duì)象被創(chuàng)建了,mock對(duì)象會(huì)記住所有的交互。然后你就可能選擇性的驗(yàn)證你感興趣的交互。
通常需要配合一些測(cè)試方法來驗(yàn)證某些行為,這些方法稱為"打樁方法"(Stub),打樁的意思是針對(duì)mock出來的對(duì)象進(jìn)行一些模擬操作,如設(shè)置模擬的返回值或拋出異常等。
常見的打樁方法:
| 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)行攔截 |
例如:
@Testpublic 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) 沒有打樁,因此輸出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")決定了接下來的回調(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ù)來調(diào)用某個(gè)方法的真實(shí)實(shí)現(xiàn)方法
注意,在Mock環(huán)境下,所有的對(duì)象都是模擬出來的,而方法的結(jié)果也是需要模擬出來的,如果你沒有為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()方法沒有設(shè)置模擬返回值,而getName()返回值是String類型的,因此直接調(diào)用的話會(huì)返回String的默認(rèn)值null,所以上面代碼如果要想輸出getName()方法的真實(shí)返回值的話,需要設(shè)置doCallRealMethod():
@Testpublic 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è)無返回值的函數(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() {@Overridepublic 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() {@Overridepublic 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ù)帶來的影響時(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ù)來測(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()來解決一些問題., 如當(dāng)你需要下面這些功能時(shí)這是必須的:
- 測(cè)試void函數(shù)
- 在受監(jiān)控的對(duì)象上測(cè)試函數(shù)
- 不只一次的測(cè)試同一個(gè)函數(shù),在測(cè)試過程中改變mock對(duì)象的行為
4. 驗(yàn)證方法的調(diào)用次數(shù)
需要配合使用一些方法
| times(int wantedNumberOfInvocations) | 驗(yàn)證調(diào)用方法的次數(shù) |
| never() | 驗(yàn)證交互沒有發(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ì)象沒有交互 |
例如:
mock.someMethod("some arg"); mock.someMethod("some arg"); //驗(yàn)證mock.someMethod("some arg")被連續(xù)調(diào)用兩次,即如果沒有調(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(); @Testpublic 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ì)象沒有交互//verifyZeroInteractions(person,person2 );} @Testpublic void testVerifyZeroInteractions() {Person person = mock(Person.class);person.eat("a");verify(person).eat("a");//注意,這將會(huì)無法到達(dá)驗(yàn)證目的,不能跟verify()混用verifyZeroInteractions(person,person2 );}5. 參數(shù)匹配器 (matchers)
Mockito以自然的java風(fēng)格來驗(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");// 輸出elementSystem.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è)值 |
一些示例代碼:
@Testpublic void testPersonAny(){when(mPerson.eat(any(String.class))).thenReturn("米飯");//或:when(mPerson.eat(anyString())).thenReturn("米飯");//輸出米飯System.out.println(mPerson.eat("面條"));}@Testpublic void testPersonContains(){when(mPerson.eat(contains("面"))).thenReturn("面條");//輸出面條System.out.println(mPerson.eat("面"));}@Testpublic void testPersonArgThat(){//自定義輸入字符長(zhǎng)度為偶數(shù)時(shí),輸出面條。when(mPerson.eat(argThat(new ArgumentMatcher<String>() {@Overridepublic 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í)行順序:
驗(yàn)證多個(gè)mock對(duì)象的函數(shù)執(zhí)行順序:
@Testpublic 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ì)象混合著來,也可以僅通過那些需要驗(yàn)證順序的mock對(duì)象來創(chuàng)建InOrder對(duì)象。
7. 使用Spy監(jiān)控真實(shí)對(duì)象
監(jiān)控真實(shí)對(duì)象使用spy()函數(shù)生成,或者也可以像@Mock那樣使用@Spy注解來生成一個(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í)也需要小心形式。
@Testpublic 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ù)被打樁了,因此這里返回的是100System.out.println(spy.size());// 驗(yàn)證交互verify(spy).add("one");verify(spy).add("two");}使用@Spy生成監(jiān)控對(duì)象:
@SpyPerson mSpyPerson;@Testpublic void testSpyPerson() {//將會(huì)輸出Person 類中g(shù)etName()的真實(shí)實(shí)現(xiàn),而不是nullSystem.out.println(mSpyPerson.getName());}理解監(jiān)控真實(shí)對(duì)象非常重要!有時(shí),在監(jiān)控對(duì)象上使用when(Object)來進(jìn)行打樁是不可能或者不切實(shí)際的。因此,當(dāng)使用監(jiān)控對(duì)象時(shí)請(qǐng)考慮doReturn|Answer|Throw()函數(shù)族來進(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()來打樁 doReturn("foo").when(spy).get(0);8. 使用ArgumentCaptor進(jìn)行參數(shù)捕獲
參數(shù)捕獲主要為了下一步的斷言做準(zhǔn)備,示例代碼:
@Testpublic void argumentCaptorTest() {List<Object> mock = mock(List.class);mock.add("John");//構(gòu)建要捕獲的參數(shù)類型,這里是StringArgumentCaptor argument = ArgumentCaptor.forClass(String.class);//在verify方法的參數(shù)中調(diào)用argument.capture()方法來捕獲輸入的參數(shù)verify(mock).add(argument.capture());//驗(yàn)證“John”參數(shù)捕獲assertEquals("John", argument.getValue());} @Testpublic 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)完成注入過程,例如:
@InjectMocksUser mTestUser;@MockAddress mAddress;@Testpublic void argumentInjectMock() {when(mAddress.getDetail()).thenReturn("浙江杭州");System.out.println(mTestUser.getAddress());}這樣就不用關(guān)心為User 設(shè)置Address ,只要為User需要依賴的類添加注解就可以了,然后直接將重點(diǎn)放到測(cè)試方法的編寫上。
或者使用@Spy監(jiān)控真實(shí)對(duì)象注入也可以:
@InjectMocksUser mTestUser;@SpyAddress mAddress;@Testpublic 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就可以解決這些問題,PowerMockito是一個(gè)擴(kuò)展了其它如EasyMock等mock框架的、功能更加強(qiáng)大的框架。PowerMock使用一個(gè)自定義類加載器和字節(jié)碼操作來模擬靜態(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 {@Testpublic 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出來的對(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 {@Testpublic 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í)需要通過PowerMockito.whenNew(File.class).withArguments("aaa").thenReturn(file)方法模擬創(chuàng)建File的操作,當(dāng)File類以aaa的參數(shù)創(chuàng)建的時(shí)候返回已經(jīng)mock出來的file對(duì)象。同時(shí)這時(shí)需要在測(cè)試類上添加注解@RunWith(PowerMockRunner.class)和@PrepareForTest(CommonExample.class),注意是在類上面添加,不是在方法上,一開始在方法上添加時(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 somethingreturn false;} } @RunWith(PowerMockRunner.class) @PrepareForTest({CommonExample.class, DependencyClass.class}) public class CommonExamplePowerMockTest {@Testpublic void callFinalMethod() {DependencyClass dependency = PowerMockito.mock(DependencyClass.class);CommonExample commonExample = new CommonExample();PowerMockito.when(dependency.isValidate()).thenReturn(true);Assert.assertTrue(commonExample.callFinalMethod(dependency));} }同樣這里mock出來需要依賴的類的對(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 {@Beforepublic void setUp() throws Exception {PowerMockito.mockStatic(Utils.class);}@Testpublic 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()方法來mock靜態(tài)類,然后就通過when().thenReturn()方法指定靜態(tài)方法的模擬返回值即可。
5. verify靜態(tài)方法的調(diào)用次數(shù)
@Testpublic 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)方法通過PowerMockito.verifyStatic(Class c)進(jìn)行驗(yàn)證,不過這里跟Mocktio有一點(diǎn)區(qū)別的是需要在這個(gè)方法的后面再調(diào)用一次靜態(tài)方法,否則不行。這里PowerMockito.verifyStatic(Utils.class)其實(shí)等同于PowerMockito.verifyStatic(Utils.class, times(1)),如果想驗(yàn)證超過一次的,那么:
@Testpublic 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)方法,總感覺這里很奇怪。。。
6. 使用真實(shí)返回值
如果在測(cè)試的過程中又遇到不需要mock出來的靜態(tài)方法的模擬返回值,而是需要真實(shí)的返回值,怎么辦呢,其實(shí)跟Mockito一樣,PowerMockito同樣提供thenCallRealMethod或者doCallRealMethod方法:
@Testpublic 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ì)象也可以:
@Testpublic 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 {@Testpublic 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的沒有太大區(qū)別,只不過Mock私有方法是通過下面的api實(shí)現(xiàn)的:
PowerMockito.when(Object instance, String methodName, Object... arguments)在PowerMockito中when函數(shù)與Mockito相比,最大的變化就是多了一些傳遞String類型的methodName的重載方法,這樣在使用上幾乎無所不能了。
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 thingreturn true;} else {return false;}}} @Testpublic void testDoSomethingIfStateReady() throws Exception {CommonExample sample = new CommonExample();Whitebox.setInternalState(sample, "mState", 1);assertThat(sample.doSomethingIfStateReady(), is(true));}通過Whitebox.setInternalState來改變私有成員變量,這種情況下不需要指定@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 {@Testpublic void testStaticVoid() throws Exception {PowerMockito.mockStatic(CommonExample.class);PowerMockito.doNothing().when(CommonExample.class, "doSomething", Mockito.any());CommonExample.doSomething("aaa");} }默認(rèn)情況下通過PowerMockito.mockStatic的靜態(tài)類的void的方法是什么也不做的,但是可以顯示的執(zhí)行doNothing, 上面的代碼將doNothing那行注釋掉也是什么也不做的。那如果想做一些事而不是doNothing呢,跟Mockito一樣,采用doAnswer:
@Testpublic void testStaticVoid() throws Exception {PowerMockito.mockStatic(CommonExample.class);PowerMockito.doAnswer(new Answer<Object>() {@Overridepublic 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 {@Testpublic 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來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è)試是沒有問題的)。另外單純的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è)試框架的使用
總結(jié)
以上是生活随笔為你收集整理的Android单元测试学习总结的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 9号天气
- 下一篇: Android实现八大行星绕太阳3D旋转