Android單元測試在複雜項目裏的落地姿式(PowerMock實踐篇)

代碼出處:colin-phang/AndroidUnitTesthtml

上篇文章:Android單元測試在複雜項目裏的落地姿式(調研篇)java

上篇《調研》的結論是:android

  1. Espresso須要跑在真機上,可用於依賴Android平臺的功能測試。
  2. Roboelctric問題太多在複雜項目中步履維艱,棄了。
  3. 考慮PowerMockito來隔離整個Android SDK以及項目業務的依賴,來保證單元測試代碼可以快速有效地編寫並執行。

文章主要分紅 調研、 實踐 兩篇。 本篇主要講講基於PowerMockito如何在項目進行Android單元測試的實踐。git

1 依賴

參考:powermock/wikigithub

testImplementation 'junit:junit:4.12'
testImplementation "org.powermock:powermock-module-junit4:2.0.2"
testImplementation "org.powermock:powermock-api-mockito2:2.0.2"
複製代碼

按照上述引入PowerMock的依賴後便可在項目test目錄下使用PowerMockito和Mockito了。api

2 使用

0 基本使用

@RunWith(PowerMockRunner.class)
@PrepareForTest( { YourClassWithEgStaticMethod.class })
public class YourTestCase {
...
}
複製代碼
  1. @RunWith,使測試代碼運行於PowerMockRunner的環境下。
  2. @PrepareForTest,當須要Mock某個類的static、final、private方法的時候,就須要聲明該註解。

上一篇提到,結合PowerMockito編寫單元測試代碼,遵循如下三個步驟:markdown

  1. Mock被依賴的複雜對象
  2. 執行被測代碼
  3. 驗證邏輯是否按照預期執行/返回

而單元測試用例的編寫,一部分取決於對業務代碼的熟悉程度,另外一方面則取決於對單元測試框架的瞭解程度,如下框架的不少用法具體仍是須要本身去搜索資料並掌握的, 具體能夠參考這兩個文檔:框架

  1. hehonghui/mockito-doc-zh
  2. powermock/powermock

上篇文章也有一個簡單的示例:PowerMockito在Android單元測試中的簡單使用,這裏再也不贅述,下面說說在編寫單元測試代碼過程當中,如何藉助PowerMockito隔離Android SDK的依賴。函數

1 建立模擬對象的2種姿式

mock

activity = PowerMockito.mock(new MainActivity())
//使activity的isFinishing方法老是返回true
when(activity.isFinishing()).thenReturn(true);
複製代碼

經過mock創造出來的對象,調用該對象全部方法都不會執行真實邏輯。必須結合when(...).then(...)來使模擬對象按照咱們預期返回。oop

spy

activity = PowerMockito.spy(new MainActivity())
//使activity的isFinishing方法老是返回false
PowerMockito.doReturn(false).when(activity).isFinishing();
複製代碼

經過spy創造模擬對象必須先手動new出來,調用該對象全部方法都會執行真實邏輯。 spy對象必須結合doReturn(...).when(...)纔會忽略真實邏輯,並按照咱們預期返回。

若是函數返回值爲void,能夠用doNothing()代替doReturn()

2 訪問/調用private

參考 powermock/wiki/Bypass-Encapsulation

有時候被測類絕大部分是private函數(好比Activity),傳統的單元測試很難覆蓋到這些private函數,固然咱們能夠經過重構/封裝使咱們的業務代碼對測試更友好,但爲了測試而對本來穩定的業務代碼進行侵入式的修改,在短時間內確定會帶來不穩定因素,這每每是團隊/領導沒法容忍的。

PowerMock的Whitebox類提供了一組api能夠獲取/修改private的變量和函數,能夠幫助咱們繞太重構去對業務代碼進行測試。

//修改私有變量
Whitebox.setInternalState(..)
//訪問私有變量
Whitebox.getInternalState(..)
//調用私有函數
Whitebox.invokeMethod(..)
//調用私有的構造函數
Whitebox.invokeConstructor(..) 
複製代碼

非靜態內部類的對象會隱式持有外部類對象,因此mock非靜態內部類,須要給」this$0「的成員變量賦值,否則單元測試代碼運行時會報錯。

Whitebox.setInternalState(innerObj, "this$0", outerObj)
複製代碼

3 抑制沒必要要的代碼邏輯執行

在實際項目中會有不少經常使用但不影響業務邏輯的代碼(Log以及其餘統計代碼等等),有些靜態代碼塊也直接調用Android SDK api。由於單元測試代碼運行在JVM上,這些代碼很容易會報錯,若是爲了測試去修改這些代碼未免有點本末倒置,因此咱們在單元測試的過程當中須要抑制/隔離這些代碼的執行。

抑制靜態變量/代碼塊的執行

PowerMockito提供了@SuppressStaticInitializationFor註解:

//在單元測試類以前聲明如下註解,能夠阻止FileUtil類的靜態代碼塊運行
@SuppressStaticInitializationFor("com.colin.unittest.FileUtil")
public class PowerMockitoSampleIII {
    ...
}
複製代碼

抑制Log等靜態函數的執行

藉助mockStatic可使指定類的靜態方法不執行。

@PrepareForTest(Log.class)
public class PowerMockitoSampleIII {
    @Before
    public void setUp() throws Exception {
    //抑制Log相關代碼的執行
    PowerMockito.mockStatic(Log.class);
    }
    ...
}
複製代碼

抑制super函數()的執行

實際業務開發中,咱們常常須要繼承Android SDK的類來進行擴展,對這些類覆寫的函數進行單元測試時,每每須要抑制父類super()的邏輯,否則在JVM中執行單元測試代碼時會報錯。

//抑制MainActivity父類的onDestroy方法
Method method = PowerMockito.method(MainActivity.class.getSuperclass(),
    "onDestroy");
PowerMockito.suppress(method);
複製代碼

3 結論

綜上所述,在Android單元測試中,經過PowerMockito來隔離整個Android SDK以及項目業務的依賴,將單元測試的重心放在較細粒度(函數級別)的代碼邏輯,徹底可行。

4 一些問題

  1. 單元測試覆蓋率: 使用了PowerMock的@PrepareForTest修飾的類單元測試覆蓋率變成0。這個問題暫時沒看到解決方案。

5 參考文章

  1. 【騰訊TMQ】用Powermock和Mockito來作安卓單元測試
  2. 【美團技術團隊】Android單元測試研究與實踐
  3. Android 單元測試實戰(1)—— 調研與選型
相關文章
相關標籤/搜索