單元測(cè)試--Android單元測(cè)試學(xué)習(xí)總結(jié)(junit+Mockito+PowerMockito)

原文鏈接:川峰-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

image

這會(huì)彈出下面的彈窗,或者鼠標(biāo)在類名上右鍵選擇菜單Go to–>Test奶陈,也會(huì)彈出下面的彈窗

image

勾選需要進(jìn)行測(cè)試的方法易阳,會(huì)自動(dòng)生成一個(gè)測(cè)試類:


image

如果勾選了@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'
    ....
}
  1. 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è)試框架的使用

原文鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末匿沛,一起剝皮案震驚了整個(gè)濱河市扫责,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌俺祠,老刑警劉巖公给,帶你破解...
    沈念sama閱讀 212,294評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蜘渣,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)肺然,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,493評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門蔫缸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人际起,你說(shuō)我怎么就攤上這事拾碌⊥麓校” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,790評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵校翔,是天一觀的道長(zhǎng)弟跑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)防症,這世上最難降的妖魔是什么孟辑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,595評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮蔫敲,結(jié)果婚禮上饲嗽,老公的妹妹穿的比我還像新娘。我一直安慰自己奈嘿,他們只是感情好貌虾,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,718評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著裙犹,像睡著了一般尽狠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叶圃,一...
    開(kāi)封第一講書(shū)人閱讀 49,906評(píng)論 1 290
  • 那天晚唇,我揣著相機(jī)與錄音,去河邊找鬼盗似。 笑死哩陕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赫舒。 我是一名探鬼主播悍及,決...
    沈念sama閱讀 39,053評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼接癌!你這毒婦竟也來(lái)了心赶?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,797評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤缺猛,失蹤者是張志新(化名)和其女友劉穎缨叫,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體荔燎,經(jīng)...
    沈念sama閱讀 44,250評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡耻姥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,570評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了有咨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琐簇。...
    茶點(diǎn)故事閱讀 38,711評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖座享,靈堂內(nèi)的尸體忽然破棺而出婉商,到底是詐尸還是另有隱情似忧,我是刑警寧澤,帶...
    沈念sama閱讀 34,388評(píng)論 4 332
  • 正文 年R本政府宣布丈秩,位于F島的核電站盯捌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蘑秽。R本人自食惡果不足惜饺著,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,018評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望筷狼。 院中可真熱鬧瓶籽,春花似錦、人聲如沸埂材。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,796評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)俏险。三九已至严拒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間竖独,已是汗流浹背裤唠。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留莹痢,地道東北人种蘸。 一個(gè)月前我還...
    沈念sama閱讀 46,461評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像竞膳,于是被迫代替她去往敵國(guó)和親航瞭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,595評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 1.Mockito是什么 Mockito是Mock框架坦辟,mock 測(cè)試就是在測(cè)試過(guò)程中刊侯,對(duì)于某些不容易構(gòu)造或者不容...
    一笑小先生閱讀 3,336評(píng)論 0 2
  • @Author:彭海波 前言 單元測(cè)試(又稱為模塊測(cè)試, Unit Testing)是針對(duì)程序模塊(軟件設(shè)計(jì)的最小...
    海波筆記閱讀 4,955評(píng)論 0 52
  • 什么是 Mock mock 的中文譯為: 仿制的,模擬的锉走,虛假的滨彻。對(duì)于測(cè)試框架來(lái)說(shuō),即構(gòu)造出一個(gè)模擬/虛假的對(duì)象挪蹭,...
    Whyn閱讀 4,342評(píng)論 0 3
  • 幾點(diǎn)說(shuō)明:代碼中的 //<== 表示跟上面的相比亭饵,這是新增的,或者是修改的代碼嚣潜,不知道怎么樣在代碼塊里面再?gòu)?qiáng)調(diào)幾行...
    鄒小創(chuàng)閱讀 13,980評(píng)論 15 41
  • 在博客Android單元測(cè)試之JUnit4中冬骚,我們簡(jiǎn)單地介紹了:什么是單元測(cè)試,為什么要用單元測(cè)試懂算,并展示了一個(gè)簡(jiǎn)...
    水木飛雪閱讀 9,415評(píng)論 4 18