Android項目中的單元測試
測試用例采用MVP + dagger架構,網絡層采用Retrofit2 + Rxjava,使用登錄做了一個簡單測試。
測試框架使用:Junit、mockito、robolectric。
一、首先看下針對MVP各層單元測試選型
在demo中,MVP各層所使用的單元測試框架如下圖所示:
- P層:不需要任何Android環境,因此使用Junit測試即可
- V層:使用Robolectric進行UI的測試
- M層:涉及到數據庫、網絡等相關操作,因此需要依賴Android環境,使用AndroidJUnitRunner進行測試
注:針對網絡測試:需要將請求網絡的異步請求轉化成同步請求,才能獲取到結果
二、測試框架引入說明
在無特殊需求的情況下可直接按下方式引入:
testImplementation 'junit:junit:4.12'//Junit測試框架,創建項目自動引入
//Mockito框架引入
testImplementation 'org.mockito:mockito-core:2.11.0'
//robolectric 開源測試ui庫
testImplementation 'org.robolectric:robolectric:3.8'
其中mockito不支持mock匿名類、final類、static方法、private方法,如需支持可以使用PowerMock解決這些問題,如使用PowerMock則需引入以下庫:
//引入PowerMock框架,必須對應mockito的版本,這里2.8.0-2.8.9 對應powermock的1.7.x版本
testImplementation 'org.mockito:mockito-core:2.8.9'
testImplementation 'org.powermock:powermock-module-junit4:1.7.3'
testImplementation 'org.powermock:powermock-api-mockito2:1.7.3'
如果引入的PowerMock使用@Rule規則進行mock則還需要引入另外兩個庫,這種方式可以有效的防止@RunWith被占用:
//注意這里是mockito2
//這里為使用@Rule方式引入要依賴的兩個庫,@Rule方式引入防止@RunWith被占用
testImplementation 'org.powermock:powermock-module-junit4-rule:1.7.3'
testImplementation 'org.powermock:powermock-classloading-xstream:1.7.3'
注:引入均使用testImplementation方式,所以測試代碼要寫在test目錄下,而非androidTest目錄
三、幾個測試框架的用法簡單描述
1.Junit框架
Junit相信很多人都熟悉,測試Java代碼基礎測試工具。在Android創建項目的時候,默認添加的依賴就有Junit4.x版本的,主要功能就是斷言。
常用的斷言方法如下:
方法名 | 方法描述 |
---|---|
assertEquals | 斷言傳入的預期值與實際值是相等的 |
assertNotEquals | 斷言傳入的預期值與實際值是不相等的 |
assertArrayEquals | 斷言傳入的預期數組與實際數組是相等的 |
assertNull | 斷言傳入的對象是不為空 |
assertNotNull | 斷言傳入的對象是不為空 |
assertTrue | 斷言條件為真 |
assertFalse | 斷言條件為假 |
assertSame | 斷言兩個對象引用同一個對象,相當于“==” |
assertNotSame | 斷言兩個對象引用不同的對象,相當于“!=” |
assertThat | 斷言實際值是否滿足指定的條件 |
注意:上面的每一個方法,都有對應的重載方法,可以在前面加一個String類型的參數,表示如果斷言失敗時的提示。
常用注解方法:
注解名 | 含義 |
---|---|
@Test | 表示此方法為測試方法 |
@Before | 在每個測試方法前執行,可做初始化操作 |
@Afte | 在每個測試方法后執行,可做釋放資源操作 |
@Ignore | 忽略的測試方法 |
@BeforeClass | 在類中所有方法前運行。此注解修飾的方法必須是static void |
@AfterClass | 在類中最后運行。此注解修飾的方法必須是static void |
@RunWith | 指定該測試類使用某個運行器 |
@Parameters | 指定測試類的測試數據集合 |
@Rule | 重新制定測試類中方法的行為 |
@FixMethodOrder | 指定測試類中方法的執行順序 |
執行順序:@BeforeClass –> @Before –> @Test –> @After –> @AfterClass
簡單例子:
public class ExampleUnitTest {
@Rule
public MyRule myRule = new MyRule();
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
2.Mockito框架使用簡單說明
在實際的單元測試中,我們測試的類之間會有或多或少的耦合,導致我們無法順利的進行測試,這時我們就可以使用Mock模擬一個對象,替換我們原先依賴的真實對象,這樣我們就可以避免外部的影響,只測試本類,得到更準確的結果。當然它的功能不僅僅只是這些,測試驅動開發也是一大亮點。
Mockito是一個簡單的Mock框架,可以幫助我們創建Mock對象,保持單元測試的獨立性。
注:Mock出的對象全都是虛擬的對象,其中非void方法都將返回默認值,比如int方法將返回0,對象方法將返回null等,而void方法將什么都不做。比如:Mock一個List對象,如果獲取這個List對象的size大小將返回是0
1.mockito有四種Mock方式,推薦使用下面方式:
@Mock //<--使用@Mock注解
public User user;
@Rule //<--使用@Rule
MockitoRule mockitoRule = MockitoJunit.rule();
2.常用的Mock打樁方法(設置預期值)如下:
通過設置預期明確Mock對象執行時會發生什么,比如返回特定的值、拋出一個異常、觸發一個事件等,又或者調用一定的次數。
方法名 | 方法描述 |
---|---|
thenReturn(T value) | 設置要返回的值 |
thenThrow(Throwable… throwables) | 設置要拋出的異常 |
thenAnswer(Answer<?> answer) | 對結果進行攔截 |
doReturn(Object toBeReturned) | 提前設置要返回的值 |
doThrow(Throwable… toBeThrown) | 提前設置要拋出的異常 |
doAnswer(Answer answer) | 提前對結果進行攔截 |
doCallRealMethod() | 調用某一個方法的真實實現 |
doNothing() | 設置void方法什么也不做 |
例子
//使用then打樁方法
@Test
public void testUserReturn(){
//使用then
//注意 !!! when參數必須調用的是method,如果調用的是公有變量,報錯誤
Mockito.when(mUser.getUserName()).thenReturn("小明");
Mockito.when(mUser.getAge()).thenThrow(new NullPointerException("性別不正確"));
//輸出小明
System.out.println(mUser.getUserName());
//拋出異常
System.out.println(mUser.getAge());
}
特別說明:Mock出的對象只能通過以上方法進行設置預期值,如果直接調用對象中的方法去設值,將是無效的。例如:Mock一個List對象,如果調用add方法添加值是無效的。
3.常用驗證方法
有時候我們可能不關心返回結果,而是關心方法有否被正確的參數調用過,這時候就應該使用驗證方法了。
verify(T mock)驗證發生的某些行為 。
方法名 | 方法描述 |
---|---|
after(long millis) | 在給定的時間后進行驗證 |
timeout(long millis) | 驗證方法執行是否超時 |
atLeast(int minNumberOfInvocations) | 至少進行n次驗證 |
atMost(int maxNumberOfInvocations) | 至多進行n次驗證 |
description(String description) | 驗證失敗時輸出的內容 |
times(int wantedNumberOfInvocations) | 驗證調用方法的次數 |
never() | 驗證交互沒有發生,相當于times(0) |
only() | 驗證方法只被調用一次,相當于times(1) |
例子
//after 在給定的時間后進行驗證
Mockito.verify(mUser,Mockito.after(1000)).getAge();
//至少驗證2次
Mockito.verify(mUser, Mockito.atLeast(2)).getAge();
4.Spy一個真實對象
Mock操作的全是虛擬對象,比如我們Mock一個List,即使設置了Mockito.when(list.get(0)).thenReturn("hello"),實際上list的size大小扔為0。那么我們如果想操作一個真實的對象怎么辦呢?
其實Mockito給我們提供了一個對真實對象操作的辦法,就是Spy。下面看下例子,就能明白了
@Test
public void testList(){
List<String> list0 = new ArrayList<>();
List list = Mockito.spy(list0);
list.add("1111111");
list.add("222222222");
//設置預期值,必須在不越界的情況下設置,否則會越界
Mockito.when(list.get(0)).thenReturn("hello");
System.out.println(list.get(0));
System.out.println(list.get(1));
System.out.println("list size : " + list.size());
System.out.println("list0 size : " + list.size());
}
打印的結果如下:
hello
222222222
list size : 2
list0 size : 2
5.Mockito還有其他的用法
可以參考:Mockito框架的使用
如需用到PowerMock參考:PowerMock使用
3.Robolectric框架使用簡單說明
Robolectric通過實現一套JVM能運行的Android代碼,從而做到脫離Android運行環境進行測試。
1.使用方法
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class)
public class MainActivityTest {
}
2.日志輸出轉換
Java中大多使用System.out.println()輸出日志,而在Android中多使用Log。但是在單元測試中無法輸出Log的信息,這時我們就要使用ShadowLog進行轉換。
@Before
public void setUp(){
//輸出日志
ShadowLog.stream = System.out;
}
這樣 Log日志都將輸出在控制面板中。
3.robolectric能做的事情
可以驗證Activity、ui組件、service、BroadcastReceiver、Dialog、Toast、Fragment、獲取Application、訪問資源以及驗證Activity和Fragment的生命周期等,功能十分強大,主要使用到ShadowXX做驗證。
比如之前例子中的ShadowActivity、ShadowLog、ShadowAlertDialog等。Shadow在實現的同時,拓展了原本的Android代碼,實現了許多便于測試的功能,比如例子中用到的 getNextStartedActivity、ShadowToast.getTextOfLatestToast()、ShadowAlertDialog.getLatestAlertDialog()。同時也可以自定義自己的Shadow。
具體例子可以參考:demo工程test目錄下的robolectric
4.參考資料
四、測試代碼覆蓋率
1.配置
一般情況下,需要在Android studio中做如下配置才能查看到覆蓋率
2.查看覆蓋率
做完上面配置,點擊一個測試的類,使用Run xx with Coverage,運行完成就會彈出一個覆蓋率界面,如下圖:
覆蓋率可以導出,格式是html,可以直接使用瀏覽器查看,十分方便。
另外也有使用Jacoco做代碼覆蓋率的,使用起來需要配置gradle,在Activity的oncreate和onDestroy方法中做寫入處理,同時還要配置寫入權限等,所以個人認為比較麻煩,沒用Android studio這個自帶的覆蓋率使用方便。當然如果有興趣的可以研究下,提供兩個參考地址:Jacoco插件、Android studio中使用Jacoco
最后附上例子源碼:點擊查看