摘要:前言越來越多的項(xiàng)目開始嘗試寫單元測試,關(guān)于單元測試的好處以及原理已經(jīng)有很多資料了,這里不在做過多的講述,本文主要介紹單元測試在模塊化應(yīng)用中的一些思考,以及如何優(yōu)雅的寫單元測試。最后,依賴注入來寫單元測試。
本文由作者潘威授權(quán)網(wǎng)易云社區(qū)發(fā)布。
前言
越來越多的項(xiàng)目開始嘗試寫單元測試,關(guān)于單元測試的好處以及原理已經(jīng)有很多資料了,這里不在做過多的講述,本文主要介紹單元測試在模塊化應(yīng)用中的一些思考,以及如何優(yōu)雅的寫單元測試。
易于測試的代碼
單元測試最大的痛點(diǎn)就是代碼耦合,比如直接持有第三方庫的引用、不合理的跨層調(diào)用等等,除此之外,static method、new object、singleton 都是不利于測試的代碼方式, 這就意味著需要 mock 大量的替身類,增加了測試成本,應(yīng)該盡量避免,同時使用依賴注入的方式來代替。
如何做好單元測試?
首先,在模塊化應(yīng)用中應(yīng)該創(chuàng)建公共的單元測試模塊,里面可以放一些公共的 BaseTest、Shadow Class、Utils、Junit rules 等等,在業(yè)務(wù)模塊中直接 dependency 進(jìn)來即可,提高寫單元測試的效率。
其次,明確需要測試的代碼。剛開始的時候,可以只測中間邏輯層和工具類,這部分代碼相對“干凈”,易于測試,也是邏輯分支最集中的地方。
最后,依賴注入來寫單元測試。試想一下 mock 的類都能夠自動完成注入,是不是很爽?這樣能大大提高編寫測試用例的速度,避免重復(fù)的 mock 替身類和靜態(tài)方法,并提高測試代碼的可讀性。
所以,我們引入了DI框架來做這件事情!
1、開發(fā)階段
我們只需要在一個類似于 dependency 工廠的地方統(tǒng)一生產(chǎn)這些 dependency 對象,以及這些 dependency 的 dependency。所有需要用到這些 dependency 的地方都從這個工廠里面去獲取。
2、測試階段
定義一個同樣的 dependency 工廠,不同的是,該工廠生產(chǎn)的是測試所需要的 Shadow 替身,能夠自動識別依賴關(guān)系,并實(shí)現(xiàn)自動注入!
Dagger2 的應(yīng)用
沒錯!前面提到的 DI 框架就是 Dagger2,為了降低風(fēng)險并減少使用成本,選擇了一個模塊進(jìn)行嘗試,Dagger2 既能實(shí)現(xiàn)模塊內(nèi)的自動注入,又能向外提供注入能力,實(shí)現(xiàn)跨模塊的注入。
在 Dagger2 里,生產(chǎn)這些 dependency 的工廠叫做 Module ,然而使用者并不是直接向 Module 要 dependency,而是有一個專門的“工廠管理員”,負(fù)責(zé)接收使用者的要求,然后到 Module 里面去找到相應(yīng)的 dependency 對象,最后提供給使用者。這個“工廠管理員”叫做 Component。基本上,這就是 Dagger2 里面最重要的兩個概念。
上圖是 Dagger2 在模塊之間的依賴關(guān)系,本文只介紹模塊內(nèi)的應(yīng)用以及單元測試的實(shí)現(xiàn)。
1、創(chuàng)建模塊級的 LibComponent 和 LibModule
LibModule里面定義了整個模塊都要用的dependency,比如PersonalContentInstance 、Scope、 DataSource等等,所以DaggerLibComponent的存在是唯一的,在模塊初始化的時候創(chuàng)建好,放在一個地方便于獲取。
mInstance.mComponent = DaggerPersonalContentLibComponent.builder()
.personnalContentLibModule(new PersonnalContentLibModule()) .build();
2、創(chuàng)建 Frame 級別的 FrameComponent 和 FrameModule
FrameModule 里面定義了某個頁面用到的 dependency,比如 Context、Handler、Logic、Adapter 等等,每個頁面對應(yīng)一個 DaggerFrameComponent,在頁面的 onCreate() 里面創(chuàng)建好。
3、FrameComponent 依賴于 LibComponent
在 Frame 中可以享受到 LibComponent 中全局依賴的注入,只需要在頁面初始化的時候完成注入即可。
DaggerFrameComponent.builder()
.libComponent(mInstance.getComponent()) .frameModule(new FrameModule(this)) .build() .injectMembers(this);
再看看單元測試?yán)锩嫒绾蝸韒ock dependency? 比如,LearnRecordDetailLogic 會調(diào)用mScope 和 mDataSource 中的方法,而 IPersonalContentScope 和 IDataSource 的實(shí)例對象是從 Dagger2 的 Component 里面獲取的,怎樣把 mScope 和 mDataSource 給 mock 掉呢?
實(shí)際上,LearnRecordDetailLogic 向 DaggerLibComponent 獲取實(shí)例調(diào)用的是 PersonnalContentLibModule 中的 provideDataSource() 和 provideScope() 方法,最后返回給 LearnRecordDetailLogic ,也就是說,真正實(shí)例化 IPersonalContentScope 和 IDataSource 的地方是在 PersonnalContentLibModule。
@Modulepublic class PersonnalContentLibModule {
...... @PerLibrary @Provides PersonalContentInstance providePersonalContentInstance() { return PersonalContentInstance.getInstance(); } @PerLibrary @Provides IPersonalContentScope provideScope(PersonalContentInstance instance) { return instance.getScope(); } @PerLibrary @Provides IDataSource provideDataSource(PersonalContentInstance instance) { return instance.getDataSourse(); }
}
前面創(chuàng)建 DaggerLibComponent 的時候,給它的 builder 傳遞了一個 PersonnalContentLibModule 對象,如果我們傳給 DaggerLibComponent 的 Module 是一個 TestModule,在它的 provide 方法被調(diào)用時,返回一個 mock 的 IPersonalContentScope 和 IDataSource,那么在測試代碼中獲得的,不就是 mock 后的替身對象嗎?
public class PersonnalContentLibTestModule extends PersonnalContentLibModule {
...... @Override PersonalContentInstance providePersonalContentInstance() { return PowerMockito.mock(PersonalContentInstance.class); } @Override IPersonalContentScope provideScope(PersonalContentInstance instance) { return PowerMockito.mock(IPersonalContentScope.class); } @Override IDataSource provideDataSource(PersonalContentInstance instance) { return PowerMockito.mock(IDataSource.class); }
}
以上就是 Dagger2 在單元測試?yán)锏膽?yīng)用。在 LibModule 的基礎(chǔ)上派生出一個 LibTestModule,除此之外,LearnRecordDetailLogic 還用到了 Context 和 Handler 對象,所以需要創(chuàng)建一個Frame級別的 Module,然后 override 掉 provide方法,讓它返回你想要的 mock 對象。
看一下效果,越復(fù)雜的類越能發(fā)揮出 Dagger2 的威力!
//使用dagger之前mContext = mock(Context.class);
mHandler = mock(Handler.class);
mDataSource = mock(IDataSource.class);
mScope = mock(IPersonalContentScope.class);
mContentInstance = mock(PersonalContentInstance.class);
when(mContentInstance.getDataSourse()).thenReturn(mDataSource);
when(mContentInstance.getScope()).thenReturn(mScope);
mockStatic(PersonalContentInstance.class);
when(PersonalContentInstance.getInstance()).thenReturn(mContentInstance);//daggerDaggerFrameTestComponent.builder()
.libComponent(ComponentUtil.getLibTestComponent) .frameTestModule(new FrameTestModule()) .build() .inject(this);
總結(jié)
本文介紹了 Dagger2 在模塊內(nèi)以及單元測試中的應(yīng)用,DI是一種很好的開發(fā)模式,即使不做單元測試,也會讓我們的代碼更加簡潔、干凈、解耦,只不過在單元測試中發(fā)揮出了更大的威力,讓很多難測的代碼測試起來更加容易。
最后,介紹一下 Dagger2 的配置方法:
在模塊的 build.gradle 中添加
dependencies {
//other dependencies //Dagger2 compile "com.google.dagger:dagger:${DAGGER_VERSION}" annotationProcessor "com.google.dagger:dagger-compiler:${DAGGER_VERSION}"}
正常情況下,main 目錄下的源代碼 build 后,生成代碼放在 /build/generated/source/apt/buildType 下面,但是 test 目錄下的測試代碼,在 compile-time 階段卻無法識別。查看 build 目錄,發(fā)現(xiàn)存在這部分代碼,但是無法正常 import 進(jìn)來。所以還需要在 build.gradle 中添加如下代碼:
android.libraryVariants.all { def aptOutputDir = new File(buildDir, "generated/source/apt/${it.unitTestVariant.dirName}")
it.unitTestVariant.addJavaSourceFoldersToModel(aptOutputDir)
}
免費(fèi)領(lǐng)取驗(yàn)證碼、內(nèi)容安全、短信發(fā)送、直播點(diǎn)播體驗(yàn)包及云服務(wù)器等套餐
更多網(wǎng)易技術(shù)、產(chǎn)品、運(yùn)營經(jīng)驗(yàn)分享請訪問網(wǎng)易云社區(qū)。
文章來源: 網(wǎng)易云社區(qū)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/25359.html
摘要:必然的,他們會拋棄標(biāo)準(zhǔn)庫中的,使用或者發(fā)明自己心儀的單元測試框架。究其原因,一些人會說時間寫代碼都不夠,哪還有空寫單元測試。最后我的個人觀點(diǎn),單元測試其實(shí)還有一個非常重要的作用,就是替代函數(shù)文檔注釋。希望從今天起,你的代碼也都有單元測試。 單元測試是每種編程語言必學(xué)的課題,是保護(hù)開發(fā)者的強(qiáng)力護(hù)盾,每個程序員都在時間允許的情況下盡可能多的寫單元測試,今天我們不討論其必要性,只拋磚引玉聊一...
摘要:實(shí)際開發(fā)中,如果每個包都去走一遍這些步驟,步驟好像確實(shí)有點(diǎn)多。 歡迎大家前往騰訊云+社區(qū),獲取更多騰訊海量技術(shù)實(shí)踐干貨哦~ 本文由小明plus發(fā)表 很多時候,我們可能想要用 typescript 語言來創(chuàng)建一些模塊,并提交到 npm 供別人使用, 那么在 2018 年,如果我想要初始化這樣的一個模塊,我需要做哪些步驟呢?: 答案是:創(chuàng)建一個優(yōu)雅的,對開發(fā)者友好的模塊,至少需要以下 15...
摘要:我寫過一些開源項(xiàng)目,在開源方面有一些經(jīng)驗(yàn),最近開到了阮老師的微博,深有感觸,現(xiàn)在一個開源項(xiàng)目涉及的東西確實(shí)挺多的,特別是對于新手來說非常不友好最近我寫了一個,旨在從多方面快速幫大家搭建一個標(biāo)準(zhǔn)的庫,本文將已為例,介紹寫一個開源庫的知識 我寫過一些開源項(xiàng)目,在開源方面有一些經(jīng)驗(yàn),最近開到了阮老師的微博,深有感觸,現(xiàn)在一個開源項(xiàng)目涉及的東西確實(shí)挺多的,特別是對于新手來說非常不友好 show...
摘要:概述是一個輕量級的單元測試工具,基于二次開發(fā),使用它基于注解的方式,快速在本地進(jìn)行單元壓測并提供詳細(xì)的報告。當(dāng)和都有指定時,以執(zhí)行次數(shù)多的為準(zhǔn)。測試報告最終的測試報告位于,使用瀏覽器打開即可。 概述 ContiPerf 是一個輕量級的單元測試工具,基于JUnit 4二次開發(fā),使用它基于注解的方式,快速在本地進(jìn)行單元壓測并提供詳細(xì)的報告。 Example 1. 新建 SpringBoot...
閱讀 3794·2023-04-25 16:32
閱讀 2194·2021-09-28 09:36
閱讀 2034·2021-09-06 15:02
閱讀 673·2021-09-02 15:21
閱讀 918·2019-08-30 15:56
閱讀 3513·2019-08-30 15:45
閱讀 1708·2019-08-30 13:09
閱讀 379·2019-08-29 16:05