日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android单元测试学习总结

發布時間:2024/3/24 Android 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android单元测试学习总结 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

      • 一、本地單元測試
        • 1. 創建測試類
        • 2. Assert類中的常用斷言方法
        • 3. 運行測試類
        • 4. 運行單個測試方法或多個測試類
      • 二、Mockito測試框架的使用
        • 1. Mock概念的理解
        • 2. Mockito中幾種Mock對象的方式
        • 3. 驗證行為
          • verify(T mock)函數的使用
          • 使用`when(T methodCall)`函數
          • 使用`thenAnswer`為回調做測試樁
          • 使用`doCallRealMethod()`函數來調用某個方法的真實實現方法
          • 使用`doNothing()`函數是為了設置void函數什么也不做
          • 使用`doAnswer()`函數測試void函數的回調
          • 需要使用doReturn函數代替thenReturn的情況
          • 使用`doThrow()`函數來測試void函數拋出異常
        • 4. 驗證方法的調用次數
        • 5. 參數匹配器 (matchers)
        • 6. 使用InOrder驗證執行執行順序
        • 7. 使用Spy監控真實對象
        • 8. 使用ArgumentCaptor進行參數捕獲
        • 9. 使用@InjectMocks自動注入依賴對象
      • 三、PowerMockito框架使用
        • 1. 普通Mock的方式
        • 2. Mock方法內部new出來的對象
        • 3. Mock普通對象的final方法
        • 4. Mock普通類的靜態方法
        • 5. verify靜態方法的調用次數
        • 6. 使用真實返回值
        • 7. Mock私有方法
        • 8. Mock普通類的私有變量
        • 9. 對靜態void方法進行Mock
        • 10. Mock系統的final靜態類
      • 四、Robolectric測試框架的使用
      • 五、Espresso測試框架的使用

Android單元測試主要分為以下兩種

  • 本地單元測試(Junit Test), 本地單元測試是純java代碼的測試,只運行在本地電腦的JVM環境上,不依賴于Android框架的任何api, 因此執行速度快,效率較高,但是無法測試Android相關的代碼。
  • 儀器化測試(Android Test),是針對Android相關代碼的測試,需要運行在真機設備或模擬器上,運行速度較慢,但是可以測試UI的交互以及對設備信息的訪問,得到接近真實的測試結果。

在Android Studio中新建一個項目的時候,app的gradle中會默認添加單元測試的相關依賴庫:

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添加的依賴就是本地化測試庫, androidTestImplementation 添加的依賴則是Android環境下的測試庫,同時,在項目的工程目錄下也會默認創建好測試的目錄:

其中app/src/test/下面存放的是Junit本地測試代碼,app/src/androidTest/下面存放的是Android測試代碼。

一、本地單元測試

進行本地單元測試需要先了解一些基本的Junit注解:

注解名稱含義
@Test定義所在方法為單元測試方法,方法必須是public void
@Before定義所在方法在每個測試用例執行之前執行一次, 用于準備測試環境(如: 初始化類,讀輸入流等),在一個測試類中,每個@Test方法的執行都會觸發一次調用
@After定義所在方法在每個測試用例執行之后執行一次,用于清理測試環境數據,在一個測試類中,每個@Test方法的執行都會觸發一次調用。
@BeforeClass定義所在方法在測試類里的所有用例運行之前運行一次,方法必須是public static void,用于做一些耗時的初始化工作(如: 連接數據庫)
@AfterClass定義所在方法在測試類里的所有用例運行之后運行一次,方法必須是public static void,用于清理數據(如: 斷開數據連接)
@Test (expected = Exception.class)如果該測試方法沒有拋出Annotation中的Exception類型(子類也可以),則測試失敗
@Test(timeout=100)如果該測試方法耗時超過100毫秒,則測試失敗,用于性能測試
@Ignore 或者 @Ignore(“太耗時”)忽略當前測試方法,一般用于測試方法還沒有準備好,或者太耗時之類的
@FixMethodOrder定義所在的測試類中的所有測試方法都按照固定的順序執行,可以指定3個值,分別是DEFAULT、JVM、NAME_ASCENDING(字母順序)
@RunWith指定測試類的測試運行器

更多可以參考Junit官網:https://junit.org/junit4/

1. 創建測試類

接下來就可以創建測試類,除了可以手動創建測試類外,可以利用AS快捷鍵:將光標選中要創建測試類的類名上->按下ALT + ENTER->在彈出的彈窗中選擇Create Test

這會彈出下面的彈窗,或者鼠標在類名上右鍵選擇菜單Go to–>Test,也會彈出下面的彈窗

勾選需要進行測試的方法,會自動生成一個測試類:

如果勾選了@Before或@After的話也會自動給你生成對應的測試方法

接下來編寫測試方法,首先在要測試的目標類中寫幾個業務方法:

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 "";} }

然后,測試類:

@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()是自動生成的添加了@Before注解,這會在每個測試方法執行前執行,因此在這里創建一個目標對象,也可以選擇添加@BeforeClass注解但這時setUp()應該改為靜態的方法。然后在每個測試方法中編寫測試用例,這里使用org.junit.Assert包中的斷言方法,有很多assertXXX方法,可以自己選擇用來判斷目標方法的結果是否滿足預期。

2. Assert類中的常用斷言方法

方法含義
assertNull(Object object)斷言對象為空
assertNull(String message, Object object)斷言對象為空,如果不為空拋出異常攜帶指定的message信息
assertNotNull(Object object)斷言對象不為空
assertNotNull(Object object)斷言對象不為空,如果為空拋出異常攜帶指定的message信息
assertSame(Object expected, Object actual)斷言兩個對象引用的是同一個對象
assertSame(String message, Object expected, Object actual)斷言兩個對象引用的是同一個對象,否則拋出異常攜帶指定的message信息
assertNotSame(Object expected, Object actual)斷言兩個對象引用的不是同一個對象
assertNotSame(String message, Object expected, Object actual)斷言兩個對象引用的不是同一個對象,否則拋出異常攜帶指定的message信息
assertTrue(boolean condition)斷言結果為true
assertTrue(String message, boolean condition)斷言結果為true, 為false時拋出異常攜帶指定的message信息
assertFalse(boolean condition)斷言結果為false
assertFalse(String message, boolean condition)斷言結果為false, 為true時拋出異常攜帶指定的message信息
assertEquals(long expected, long actual)斷言兩個long 類型 expected 和 actual 的值相等
assertEquals(String message, long expected, long actual)斷言兩個long 類型 expected 和 actual 的值相等,如不相等則拋異常攜帶指定message信息
assertEquals(Object expected, Object actual)斷言兩個對象相等
assertEquals(String message, Object expected, Object actual)斷言兩個對象相等,如果不相等則拋出異常攜帶指定的message信息
assertEquals(float expected, float actual, float delta)斷言兩個 float 類型 expect 和 actual 在 delta 偏差值下相等,delta是誤差精度
assertEquals(String message, float expected, float actual, float delta)斷言兩個 float 類型 expect 和 actual 在 delta 偏差值下相等,如果不相等則拋出異常攜帶指定的message信息
assertEquals(double expected, double actual, double delta)斷言兩個 double 類型 expect 和 actual 在 delta 偏差值下相等
assertEquals(String message, double expected,double actual, double delta)斷言兩個 double 類型 expect 和 actual 在 delta 偏差值下相等,如果不相等則拋出異常攜帶指定的message信息
assertArrayEquals(T[] expected, T[] actual)斷言兩個相同類型的數組的元素一一對應相等
assertArrayEquals(String message, T[] expected, T[] actual)斷言兩個相同類型的數組的元素一一對應相等,如果不相等則拋出異常攜帶指定的message信息
fail()直接讓測試失敗
fail(String message)直接讓測試失敗并給出message錯誤信息
assertThat(T actual, Matcher<? super T> matcher)斷言actual和matcher規則匹配
assertThat(String reason, T actual, Matcher<? super T> matcher)斷言actual和matcher規則匹配,否則拋出異常攜帶指定的reason信息

其中assertEquals的方法,都對應有一個assertNotEquals方法,這里不列了,assertThat是一個強大的方法:

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"));

需要靜態導入org.hamcrest.Matchers類里面的方法,更多匹配方法請參考這個類。

3. 運行測試類

選中測試類右鍵Run運行,控制面板中就會顯示測試結果:

如果所有的測試用例都正常返回了預期的結果,則面板中左側每個測試方法前面會帶一個綠色的對勾,否則方法前面會變成紅色感嘆號并且控制面板會輸出異常,現在來改一個業務方法試一下:

public boolean isTeenager(int age) {if (age < 15) {return false;}return false;}

這里將age < 15改為輸出false,假設這是我們在編碼的時候由于疏忽粗心造成的,然后運行測試類:

控制面板會告訴那一行出錯了:

也就是說這里沒有返回預期的結果,說明我們編寫的業務邏輯是有錯誤的,這時就需要改bug了。

4. 運行單個測試方法或多個測試類

上面是運行的整個測試類,如果要運行測試類的單個方法,則鼠標只選中某個要運行的測試方法,然后右鍵選擇Run即可。如果要同時運行多個測試類,而如果多個測試類在同一個包下面,則選中多個測試類所在的包目錄,然后右鍵選擇Run運行。否則可以通過下面的方式指定,創建一個空的測試類,然后添加注解:

@RunWith(Suite.class) @Suite.SuiteClasses({SimpleClassTest.class, SimpleClass2Test.class}) public class RunMultiTest { }

運行這個測試類就可以將指定的測試類的方法一起運行。

二、Mockito測試框架的使用

前面介紹的只能測試不涉及Android相關Api的java代碼用例,如果涉及到Android相關Api的時候,就不方便了,這時如果不依賴第三方庫的話可能需要使用儀器化測試跑到Android設備上去運行,于是有一些比較好的第三方的替代框架可以來模擬使用Android的代碼測試,Mockito就是基于依賴注入實現的一個測試框架。

1. Mock概念的理解

什么是Mock, 這個單詞的中文意思就是“模仿”或者“虛假”的意思,也就是要模仿一個對象,為啥要模仿?
在傳統的JUnit單元測試中,沒有消除在測試中對對象的依賴,如A對象依賴B對象方法,在測試A對象的時候,我們需要構造出B對象,這樣子增加了測試的難度,或者使得我們對某些類的測試無法實現。這與單元測試的思路相違背。
還有一個主要的問題就是本地單元測試由于是運行本地JVM環境,無法依賴Android的api,只靠純Junit的測試環境很難模擬出完整的Android環境,導致無法測試Android相關的代碼,而Mock就能解決這個問題,通過Mock能夠很輕易的實現對象的模擬。

添加依賴:

dependencies {implementation fileTree(dir: 'libs', include: ['*.jar'])testImplementation 'org.mockito:mockito-core:2.19.0'.... }

2. Mockito中幾種Mock對象的方式

使用之前通過靜態方式導入會使用更方便:

// 靜態導入會使代碼更簡潔import static org.mockito.Mockito.*;

直接mock一個對象:

@Testpublic void testMock() {SimpleClass mockSimple = Mockito.mock(SimpleClass.class);assertNotNull(mockSimple);}

注解方式mock一個對象:

@MockSimpleClass simple;@Beforepublic void setUp() {MockitoAnnotations.initMocks(this);}@Testpublic void testMock() {assertNotNull(simple);}

運行器方式mock一個對象:

@RunWith(MockitoJUnitRunner.class) public class ExampleUnitTest {@MockSimpleClass simple;@Testpublic void testMock() {assertNotNull(simple);} }

MockitoRule方式mock一個對象:

public class ExampleUnitTest {@MockSimpleClass simple;@Rule //<--使用@Rulepublic MockitoRule mockitoRule = MockitoJUnit.rule();@Testpublic void testMock() {assertNotNull(simple);} }

3. 驗證行為

verify(T mock)函數的使用

verify(T mock)的作用是驗證發生的某些行為等同于verify(mock, times(1)) 例如:

@Test public void testMock() {//創建mock對象List mockedList = mock(List.class);//使用mock對象mockedList.add("one");mockedList.clear();//驗證mockedList.add("one")是否被調用,如果被調用則當前測試方法通過,否則失敗verify(mockedList).add("one");//驗證 mockedList.clear()是否被調用,如果被調用則當前測試方法通過,否則失敗verify(mockedList).clear();} @Test public void testMock() {mock.someMethod("some arg");//驗證mock.someMethod("some arg")是否被調用,如果被調用則測試方法通過,否則失敗verify(mock).someMethod("some arg");}

也就是說如果把調用的方法注釋掉,則運行testMock()方法就會失敗。

通過verify關鍵字,一旦mock對象被創建了,mock對象會記住所有的交互。然后你就可能選擇性的驗證你感興趣的交互。

通常需要配合一些測試方法來驗證某些行為,這些方法稱為"打樁方法"(Stub),打樁的意思是針對mock出來的對象進行一些模擬操作,如設置模擬的返回值或拋出異常等。

常見的打樁方法:

方法名方法含義
doReturn(Object toBeReturned)提前設置要返回的值
doThrow(Throwable… toBeThrown)提前設置要拋出的異常
doAnswer(Answer answer)提前對結果進行攔截
doCallRealMethod()調用某一個方法的真實實現
doNothing()設置void函數什么也不做
thenReturn(T value)設置要返回的值
thenThrow(Throwable… throwables)設置要拋出的異常
thenAnswer(Answer<?> answer)對結果進行攔截

例如:

@Testpublic void testMock() {// 你可以mock具體的類型,不僅只是接口List mockedList = mock(List.class);// 打測試樁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));// 因為get(999) 沒有打樁,因此輸出null, 注意模擬環境下這個地方是不會報IndexOutOfBoundsException異常的System.out.println(mockedList.get(999));// get(1)時會拋出異常System.out.println(mockedList.get(1));// clear會拋出異常mockedList.clear();}

doXXX和thenXXX使用上差不多,一個是調用方法之前設置好返回值,一個是在調用方法之后設置返回值。默認情況下,Mock出的對象的所有非void函數都有返回值,對象類型的默認返回的是null,例如返回int、boolean、String的函數,默認返回值分別是0、false和null。

使用when(T methodCall)函數

打樁方法需要配合when(T methodCall)函數,意思是使測試樁方法生效。當你想讓這個mock能調用特定的方法返回特定的值,那么你就可以使用它。

例如:

when(mock.someMethod()).thenReturn(10);//你可以使用靈活的參數匹配,例如 when(mock.someMethod(anyString())).thenReturn(10);//設置拋出的異常when(mock.someMethod("some arg")).thenThrow(new RuntimeException());//你可以對不同作用的連續回調的方法打測試樁://最后面的測試樁(例如:返回一個對象:"foo")決定了接下來的回調方法以及它的行為。when(mock.someMethod("some arg")).thenReturn("foo")//第一次調用someMethod("some arg")會返回"foo".thenThrow(new RuntimeException());//第二次調用someMethod("some arg")會拋異常//可以用以下方式替代比較小版本的連貫測試樁:when(mock.someMethod("some arg")).thenReturn("one", "two");//和下面的方式效果是一樣的when(mock.someMethod("some arg")).thenReturn("one").thenReturn("two");//比較小版本的連貫測試樁并且拋出異常:when(mock.someMethod("some arg")).thenThrow(new RuntimeException(), new NullPointerException();
使用thenAnswer為回調做測試樁
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()函數來調用某個方法的真實實現方法

注意,在Mock環境下,所有的對象都是模擬出來的,而方法的結果也是需要模擬出來的,如果你沒有為mock出的對象設置模擬結果,則會返回默認值,例如:

public class Person {public String getName() {return "小明";} }@Test public void testPerson() {Person mock = mock(Person.class);//輸出null,除非設置發回模擬值when(mock.getName()).thenReturn("xxx");System.out.println(mock.getName()); }

因為getName()方法沒有設置模擬返回值,而getName()返回值是String類型的,因此直接調用的話會返回String的默認值null,所以上面代碼如果要想輸出getName()方法的真實返回值的話,需要設置doCallRealMethod():

@Testpublic void testPerson() {Person mock = mock(Person.class);doCallRealMethod().when(mock).getName();//輸出“小明”System.out.println(mock.getName());}
使用doNothing()函數是為了設置void函數什么也不做

需要注意的是默認情況下返回值為void的函數在mocks中是什么也不做的但是,也會有一些特殊情況。如:

測試樁連續調用一個void函數時:

doNothing().doThrow(new RuntimeException()).when(mock).someVoidMethod();//does nothing the first time:mock.someVoidMethod();//throws RuntimeException the next time:mock.someVoidMethod();

監控真實的對象并且你想讓void函數什么也不做:

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()函數測試void函數的回調

當你想要測試一個無返回值的函數時,可以使用一個含有泛型類Answer參數的doAnswer()函數做回調測試。假設你有一個void方法有多個回調參數,當你想指定執行某個回調時,使用thenAnswer很難實現了,如果使用doAnswer()將非常簡單,示例代碼如下:

MyCallback callback = mock(MyCallback.class); Mockito.doAnswer(new Answer() {@Overridepublic Object answer(InvocationOnMock invocationOnMock) throws Throwable {//獲取第一個參數MyCallback call = invocation.getArgument(0);//指定回調執行操作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函數代替thenReturn的情況

如當監控真實的對象并且調用真實的函數帶來的影響時

List list = new LinkedList(); List spy = spy(list);//不可能完成的:真實方法被調用的時候list仍是空的,所以spy.get(0)會拋出IndexOutOfBoundsException()異常 when(spy.get(0)).thenReturn("foo");//這時你應該使用doReturn()函數 doReturn("foo").when(spy).get(0);
使用doThrow()函數來測試void函數拋出異常
SimpleClass mock = mock(SimpleClass.class); doThrow(new RuntimeException()).when(mock).someVoidMethod(); mock.someVoidMethod();

總之使用doThrow(), doAnswer(), doNothing(), doReturn() and doCallRealMethod() 這些函數時可以在適當的情況下調用when()來解決一些問題., 如當你需要下面這些功能時這是必須的:

  • 測試void函數
  • 在受監控的對象上測試函數
  • 不只一次的測試同一個函數,在測試過程中改變mock對象的行為

4. 驗證方法的調用次數

需要配合使用一些方法

方法含義
times(int wantedNumberOfInvocations)驗證調用方法的次數
never()驗證交互沒有發生,相當于times(0)
only()驗證方法只被調用一次,相當于times(1)
atLeast(int minNumberOfInvocations)至少進行n次驗證
atMost(int maxNumberOfInvocations)至多進行n次驗證
after(long millis)在給定的時間后進行驗證
timeout(long millis)驗證方法執行是否超時
description(String description)驗證失敗時輸出的內容
verifyZeroInteractions驗證mock對象沒有交互

例如:

mock.someMethod("some arg"); mock.someMethod("some arg"); //驗證mock.someMethod("some arg")被連續調用兩次,即如果沒有調用兩次則驗證失敗 verify(mock, times(2)).someMethod("some arg"); //注意,下面三種是等價的,都是驗證someMethod()被只調用一次 verify(mock).someMethod("some arg"); verify(mock, times(1)).someMethod("some arg"); verify(mock, only()).someMethod("some arg"); mPerson.getAge(); mPerson.getAge(); //驗證至少調用2次 verify(mPerson, atLeast(2)).getAge(); //驗證至多調用2次 verify(mPerson, atMost(2)).getAge(); //下面兩種等價,驗證調用次數為0 verify(mPerson, never()).getAge(); verify(mPerson, times(0)).getAge(); mPerson.getAge(); mPerson.getAge(); long current = System.currentTimeMillis(); System.out.println(current ); //延時1s后驗證mPerson.getAge()是否被執行了2次 verify(mPerson, after(1000).times(2)).getAge(); System.out.println(System.currentTimeMillis() - current); mPerson.getAge();mPerson.getAge();//驗證方法在100ms超時前被調用2次verify(mPerson, timeout(100).times(2)).getAge(); @Testpublic void testVerifyZeroInteractions() {Person person = mock(Person.class);person.eat("a");//由于person對象發生了交互,所以這里驗證失敗,把上面的調用注釋掉這里就會驗證成功verifyZeroInteractions(person);//可以驗證多個對象沒有交互//verifyZeroInteractions(person,person2 );} @Testpublic void testVerifyZeroInteractions() {Person person = mock(Person.class);person.eat("a");verify(person).eat("a");//注意,這將會無法到達驗證目的,不能跟verify()混用verifyZeroInteractions(person,person2 );}

5. 參數匹配器 (matchers)

Mockito以自然的java風格來驗證參數值: 使用equals()函數。有時,當需要額外的靈活性時你可能需要使用參數匹配器,也就是argument matchers :

// 使用內置的anyInt()參數匹配器when(mockedList.get(anyInt())).thenReturn("element");// 使用自定義的參數匹配器( 在isValid()函數中返回你自己的匹配器實現 )when(mockedList.contains(argThat(isValid()))).thenReturn("element");// 輸出elementSystem.out.println(mockedList.get(999));// 你也可以驗證參數匹配器verify(mockedList).get(anyInt());

常用的參數匹配器:

方法名含義
anyObject()匹配任何對象
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)參數包含給定的substring字符串
argThat(ArgumentMatcher matcher)創建自定義的參數匹配模式
eq(T value)匹配參數等于某個值

一些示例代碼:

@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(){//自定義輸入字符長度為偶數時,輸出面條。when(mPerson.eat(argThat(new ArgumentMatcher<String>() {@Overridepublic boolean matches(String argument) {return argument.length() % 2 == 0;}}))).thenReturn("面條");//輸出面條System.out.println(mPerson.eat("1234"));}

需要注意的是,如果你打算使用參數匹配器,那么所有參數都必須由匹配器提供。例如:

verify(mock).someMethod(anyInt(), anyString(), eq("third argument")); // 上述代碼是正確的,因為eq()也是一個參數匹配器verify(mock).someMethod(anyInt(), anyString(), "third argument"); // 上述代碼是錯誤的, 因為所有參數必須由匹配器提供,而參數"third argument"并非由參數匹配器提供,因此會拋出異常

像anyObject(), eq()這樣的匹配器函數不會返回匹配器。它們會在內部將匹配器記錄到一個棧當中,并且返回一個假的值,通常為null。

6. 使用InOrder驗證執行執行順序

驗證執行執行順序主要使用InOrder函數
如,驗證mock一個對象的函數執行順序:

@Testpublic void testInorder() {List<String> singleMock = mock(List.class);singleMock.add("小明");singleMock.add("小紅");// 為該mock對象創建一個inOrder對象InOrder inOrder = inOrder(singleMock);// 驗證add函數首先執行的是add("小明"),然后才是add("小紅"),否則測試失敗inOrder.verify(singleMock).add("小明");inOrder.verify(singleMock).add("小紅");}

驗證多個mock對象的函數執行順序:

@Testpublic void testInorderMulti() {List<String> firstMock = mock(List.class);List<String> secondMock = mock(List.class);firstMock.add("小明");secondMock.add("小紅");// 為這兩個Mock對象創建inOrder對象InOrder inOrder = inOrder(firstMock, secondMock);// 驗證它們的執行順序inOrder.verify(firstMock).add("小明");inOrder.verify(secondMock).add("小紅");}

驗證執行順序是非常靈活的,你不需要一個一個的驗證所有交互,只需要驗證你感興趣的對象即可。 你可以選擇單個mock對象和多個mock對象混合著來,也可以僅通過那些需要驗證順序的mock對象來創建InOrder對象。

7. 使用Spy監控真實對象

監控真實對象使用spy()函數生成,或者也可以像@Mock那樣使用@Spy注解來生成一個監控對象, 當你你為真實對象創建一個監控(spy)對象后,在你使用這個spy對象時真實的對象也會也調用,除非它的函數被stub了。盡量少使用spy對象,使用時也需要小心形式。

@Testpublic void testSpy() {List<String> list = new ArrayList<>();List<String> spy = spy(list);// 你可以選擇為某些函數打樁when(spy.size()).thenReturn(100);// 調用真實對象的函數spy.add("one");spy.add("two");// 輸出第一個元素"one"System.out.println(spy.get(0));// 因為size()函數被打樁了,因此這里返回的是100System.out.println(spy.size());// 驗證交互verify(spy).add("one");verify(spy).add("two");}

使用@Spy生成監控對象:

@SpyPerson mSpyPerson;@Testpublic void testSpyPerson() {//將會輸出Person 類中getName()的真實實現,而不是nullSystem.out.println(mSpyPerson.getName());}

理解監控真實對象非常重要!有時,在監控對象上使用when(Object)來進行打樁是不可能或者不切實際的。因此,當使用監控對象時請考慮doReturn|Answer|Throw()函數族來進行打樁。例如:

List list = new LinkedList(); List spy = spy(list);// 不可能實現 : 因為當調用spy.get(0)時會調用真實對象的get(0)函數, // 此時會發生IndexOutOfBoundsException異常,因為真實List對象是空的when(spy.get(0)).thenReturn("foo");// 你需要使用doReturn()來打樁 doReturn("foo").when(spy).get(0);

8. 使用ArgumentCaptor進行參數捕獲

參數捕獲主要為了下一步的斷言做準備,示例代碼:

@Testpublic void argumentCaptorTest() {List<Object> mock = mock(List.class);mock.add("John");//構建要捕獲的參數類型,這里是StringArgumentCaptor argument = ArgumentCaptor.forClass(String.class);//在verify方法的參數中調用argument.capture()方法來捕獲輸入的參數verify(mock).add(argument.capture());//驗證“John”參數捕獲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());//如果又多次參數調用,argument.getValue()捕獲到的是最后一次調用的參數assertEquals("Jim", argument.getValue());//如果要獲取所有的參數值可以調用argument.getAllValues()assertArrayEquals(new Object[]{"Brian","Jim"}, argument.getAllValues().toArray());}

9. 使用@InjectMocks自動注入依賴對象

有時我們要測試的對象內部需要依賴另一個對象,例如:

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類內部需要依賴Address類,當我們測試的時候需要mock出這兩個對象,然后將Address對象傳入到User當中,這樣如果依賴的對象多了的話就相當麻煩,Mockito 提供了可以不用去手動注入對象的方法,首先使用@InjectMocks注解需要被注入的對象,如User,然后需要被依賴注入的對象使用@Mock或@Spy注解,之后Mockito 會自動完成注入過程,例如:

@InjectMocksUser mTestUser;@MockAddress mAddress;@Testpublic void argumentInjectMock() {when(mAddress.getDetail()).thenReturn("浙江杭州");System.out.println(mTestUser.getAddress());}

這樣就不用關心為User 設置Address ,只要為User需要依賴的類添加注解就可以了,然后直接將重點放到測試方法的編寫上。

或者使用@Spy監控真實對象注入也可以:

@InjectMocksUser mTestUser;@SpyAddress mAddress;@Testpublic void argumentInjectMock() {// when(mAddress.getDetail()).thenReturn("浙江杭州");System.out.println(mTestUser.getAddress());}

其他:

連續調用的另一種更簡短的版本:

// 第一次調用時返回"one",第二次返回"two",第三次返回"three"when(mock.someMethod("some arg")).thenReturn("one", "two", "three");

參考:Mockito 中文文檔

三、PowerMockito框架使用

Mockito框架基本滿足需求但是有一些局限性,如對static、final、private等方法不能mock,PowerMockito就可以解決這些問題,PowerMockito是一個擴展了其它如EasyMock等mock框架的、功能更加強大的框架。PowerMock使用一個自定義類加載器和字節碼操作來模擬靜態方法,構造函數,final類和方法,私有方法,去除靜態初始化器等等。

添加依賴:

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的方式

目標類:

public class CommonExample {public boolean callArgumentInstance(File file) {return file.exists();} }

測試類:

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參數,基本上和單獨使用Mockito是一樣的,使用純Mockito的api也可以完成這個測試。

2. Mock方法內部new出來的對象

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"));} }

跟前面有一點區別的就是,這里要測試的方法內部創建了File對象,這時需要通過PowerMockito.whenNew(File.class).withArguments("aaa").thenReturn(file)方法模擬創建File的操作,當File類以aaa的參數創建的時候返回已經mock出來的file對象。同時這時需要在測試類上添加注解@RunWith(PowerMockRunner.class)和@PrepareForTest(CommonExample.class),注意是在類上面添加,不是在方法上,一開始在方法上添加時提示找不到測試方法,@PrepareForTest()括號里面指定的是要測試的目標類。

3. Mock普通對象的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出來需要依賴的類的對象,然后傳遞給調用方法,這里同樣需要添加@RunWith和@PrepareForTest,@PrepareForTest可以指定多個目標類,但是這里如果你只需要測試final的話,只添加DependencyClass.class一個就可以了。

4. Mock普通類的靜態方法

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中指定靜態方法所在的類,測試靜態方法之前需要調用PowerMockito.mockStatic()方法來mock靜態類,然后就通過when().thenReturn()方法指定靜態方法的模擬返回值即可。

5. verify靜態方法的調用次數

@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();}

靜態方法通過PowerMockito.verifyStatic(Class c)進行驗證,不過這里跟Mocktio有一點區別的是需要在這個方法的后面再調用一次靜態方法,否則不行。這里PowerMockito.verifyStatic(Utils.class)其實等同于PowerMockito.verifyStatic(Utils.class, times(1)),如果想驗證超過一次的,那么:

@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();}

這時PowerMockito.verifyStatic()第一個參數指定靜態方法類的Class,第二個參數接收一個VerificationMode類型的參數,因此傳遞Mockito中的任何驗證方法次數的函數都可以,Mockito中的驗證函數會返回的是一個VerificationMode類型。同樣在PowerMockito.verifyStatic方法后面要調用一次要驗證的靜態方法,總感覺這里很奇怪。。。

6. 使用真實返回值

如果在測試的過程中又遇到不需要mock出來的靜態方法的模擬返回值,而是需要真實的返回值,怎么辦呢,其實跟Mockito一樣,PowerMockito同樣提供thenCallRealMethod或者doCallRealMethod方法:

@Testpublic void testRealCall() throws Exception {PowerMockito.when(Utils.getUUId()).thenReturn("FAKE UUID");//...PowerMockito.when(Utils.getUUId()).thenCallRealMethod();//與下面等價//PowerMockito.doCallRealMethod().when(Utils.class, "getUUId");System.out.println(Utils.getUUId());}

或者直接使用spy監控真實對象也可以:

@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的沒有太大區別,只不過Mock私有方法是通過下面的api實現的:

PowerMockito.when(Object instance, String methodName, Object... arguments)

在PowerMockito中when函數與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. 對靜態void方法進行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");} }

默認情況下通過PowerMockito.mockStatic的靜態類的void的方法是什么也不做的,但是可以顯示的執行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系統的final靜態類

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中添加調用系統類所在的類,這里需要注意的是如果你使用PowerMockito來mock系統靜態final類,則gradle依賴中不能再添加單純Mockito的依賴庫,否則這里將不能mock成功,會提示Mockito can not mock/spy final class, 因為PowerMockito本身已經有對Mockito的依賴庫支持了,所以只依賴PowerMockito就可以了。除了系統靜態final類的情況,其他的情況下PowerMockito和Mockito可以同時依賴(我測試是沒有問題的)。另外單純的Mockito新版本中也支持對 final 類 final 方法的 Mock,但是需要添加配置文件并不友好。

四、Robolectric測試框架的使用

由于Robolectric部分的內容比較長,所以單獨放了一篇文章中:Android單元測試框架Robolectric的學習使用

五、Espresso測試框架的使用

Espresso是用于Android儀器化測試的測試框架,是谷歌官方主推的一個測試庫。由于Espresso部分的內容也比較長,所以單獨放了一篇文章中:Espresso測試框架的使用

總結

以上是生活随笔為你收集整理的Android单元测试学习总结的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。