上篇《調(diào)研》的結(jié)論是:
- Espresso需要跑在真機(jī)上,可用于依賴Android平臺(tái)的功能測(cè)試。
- Roboelctric問(wèn)題太多在復(fù)雜項(xiàng)目中寸步難行,棄了。
- 考慮PowerMockito來(lái)隔離整個(gè)Android SDK以及項(xiàng)目業(yè)務(wù)的依賴,來(lái)保證單元測(cè)試代碼能夠快速有效地編寫(xiě)并執(zhí)行。
文章主要分成 調(diào)研、 實(shí)踐 兩篇。 本篇主要講講基于PowerMockito如何在項(xiàng)目進(jìn)行Android單元測(cè)試的實(shí)踐。
1 依賴
testImplementation 'junit:junit:4.12'
testImplementation "org.powermock:powermock-module-junit4:2.0.2"
testImplementation "org.powermock:powermock-api-mockito2:2.0.2"
按照上述引入PowerMock的依賴后即可在項(xiàng)目test目錄下使用PowerMockito和Mockito了。
2 使用
0 基本使用
@RunWith(PowerMockRunner.class)
@PrepareForTest( { YourClassWithEgStaticMethod.class })
public class YourTestCase {
...
}
- @RunWith,使測(cè)試代碼運(yùn)行于PowerMockRunner的環(huán)境下。
- @PrepareForTest,當(dāng)需要Mock某個(gè)類的static、final、private方法的時(shí)候,就需要聲明該注解。
上一篇提到,結(jié)合PowerMockito編寫(xiě)單元測(cè)試代碼,遵循以下三個(gè)步驟:
- Mock被依賴的復(fù)雜對(duì)象
- 執(zhí)行被測(cè)代碼
- 驗(yàn)證邏輯是否按照預(yù)期執(zhí)行/返回
而單元測(cè)試用例的編寫(xiě),一部分取決于對(duì)業(yè)務(wù)代碼的熟悉程度,另一方面則取決于對(duì)單元測(cè)試框架的了解程度,以下框架的很多用法具體還是需要自己去搜索資料并掌握的,
具體可以參考這兩個(gè)文檔:
上篇文章也有一個(gè)簡(jiǎn)單的示例:PowerMockito在Android單元測(cè)試中的簡(jiǎn)單使用,這里不再贅述,下面說(shuō)說(shuō)在編寫(xiě)單元測(cè)試代碼過(guò)程中,如何借助PowerMockito隔離Android SDK的依賴。
1 創(chuàng)建模擬對(duì)象的2種姿勢(shì)
mock
activity = PowerMockito.mock(new MainActivity())
//使activity的isFinishing方法總是返回true
when(activity.isFinishing()).thenReturn(true);
通過(guò)mock創(chuàng)造出來(lái)的對(duì)象,調(diào)用該對(duì)象所有方法都不會(huì)執(zhí)行真實(shí)邏輯。必須結(jié)合when(...).then(...)來(lái)使模擬對(duì)象按照我們預(yù)期返回。
spy
activity = PowerMockito.spy(new MainActivity())
//使activity的isFinishing方法總是返回false
PowerMockito.doReturn(false).when(activity).isFinishing();
通過(guò)spy創(chuàng)造模擬對(duì)象必須先手動(dòng)new出來(lái),調(diào)用該對(duì)象所有方法都會(huì)執(zhí)行真實(shí)邏輯。
spy對(duì)象必須結(jié)合doReturn(...).when(...)才會(huì)忽略真實(shí)邏輯,并按照我們預(yù)期返回。
如果函數(shù)返回值為void,可以用
doNothing()代替doReturn()。
2 訪問(wèn)/調(diào)用private
有時(shí)候被測(cè)類絕大部分是private函數(shù)(比如Activity),傳統(tǒng)的單元測(cè)試很難覆蓋到這些private函數(shù),當(dāng)然我們可以通過(guò)重構(gòu)/封裝使我們的業(yè)務(wù)代碼對(duì)測(cè)試更友好,但為了測(cè)試而對(duì)原本穩(wěn)定的業(yè)務(wù)代碼進(jìn)行侵入式的修改,在短期內(nèi)肯定會(huì)帶來(lái)不穩(wěn)定因素,這往往是團(tuán)隊(duì)/領(lǐng)導(dǎo)無(wú)法容忍的。
PowerMock的Whitebox類提供了一組api可以獲取/修改private的變量和函數(shù),可以幫助我們繞過(guò)重構(gòu)去對(duì)業(yè)務(wù)代碼進(jìn)行測(cè)試。
//修改私有變量
Whitebox.setInternalState(..)
//訪問(wèn)私有變量
Whitebox.getInternalState(..)
//調(diào)用私有函數(shù)
Whitebox.invokeMethod(..)
//調(diào)用私有的構(gòu)造函數(shù)
Whitebox.invokeConstructor(..)
非靜態(tài)內(nèi)部類的對(duì)象會(huì)隱式持有外部類對(duì)象,所以mock非靜態(tài)內(nèi)部類,需要給”this$0“的成員變量賦值,不然單元測(cè)試代碼運(yùn)行時(shí)會(huì)報(bào)錯(cuò)。
Whitebox.setInternalState(innerObj, "this$0", outerObj)
3 抑制不必要的代碼邏輯執(zhí)行
在實(shí)際項(xiàng)目中會(huì)有很多常用但不影響業(yè)務(wù)邏輯的代碼(Log以及其他統(tǒng)計(jì)代碼等等),有些靜態(tài)代碼塊也直接調(diào)用Android SDK api。因?yàn)閱卧獪y(cè)試代碼運(yùn)行在JVM上,這些代碼很容易會(huì)報(bào)錯(cuò),如果為了測(cè)試去修改這些代碼未免有點(diǎn)本末倒置,所以我們?cè)趩卧獪y(cè)試的過(guò)程中需要抑制/隔離這些代碼的執(zhí)行。
抑制靜態(tài)變量/代碼塊的執(zhí)行
PowerMockito提供了@SuppressStaticInitializationFor注解:
//在單元測(cè)試類之前聲明以下注解,可以阻止FileUtil類的靜態(tài)代碼塊運(yùn)行
@SuppressStaticInitializationFor("com.colin.unittest.FileUtil")
public class PowerMockitoSampleIII {
...
}
抑制Log等靜態(tài)函數(shù)的執(zhí)行
借助mockStatic可以使指定類的靜態(tài)方法不執(zhí)行。
@PrepareForTest(Log.class)
public class PowerMockitoSampleIII {
@Before
public void setUp() throws Exception {
//抑制Log相關(guān)代碼的執(zhí)行
PowerMockito.mockStatic(Log.class);
}
...
}
抑制super函數(shù)()的執(zhí)行
實(shí)際業(yè)務(wù)開(kāi)發(fā)中,我們經(jīng)常需要繼承Android SDK的類來(lái)進(jìn)行擴(kuò)展,對(duì)這些類覆寫(xiě)的函數(shù)進(jìn)行單元測(cè)試時(shí),往往需要抑制父類super()的邏輯,不然在JVM中執(zhí)行單元測(cè)試代碼時(shí)會(huì)報(bào)錯(cuò)。
//抑制MainActivity父類的onDestroy方法
Method method = PowerMockito.method(MainActivity.class.getSuperclass(),
"onDestroy");
PowerMockito.suppress(method);
3 結(jié)論
綜上所述,在Android單元測(cè)試中,通過(guò)PowerMockito來(lái)隔離整個(gè)Android SDK以及項(xiàng)目業(yè)務(wù)的依賴,將單元測(cè)試的重心放在較細(xì)粒度(函數(shù)級(jí)別)的代碼邏輯,完全可行。
4 一些問(wèn)題
- 單元測(cè)試覆蓋率:
使用了PowerMock的@PrepareForTest修飾的類單元測(cè)試覆蓋率變成0。這個(gè)問(wèn)題暫時(shí)沒(méi)看到解決方案。