官方文檔鏈接:https://google.github.io/dagger/testing.html
1.前言
官網(wǎng)上還有篇關(guān)于Java中異步地依賴注入的文章浊服,由于得引入Guava包蜻韭,感覺Android上不太常用儡遮,所以沒有翻譯。若后期項目需要姻几,會再來翻譯的索烹。
使用像Dagger之類的依賴注入框架的好處之一屠列,是它讓代碼測試更簡單。下面探討一些測試Dagger構(gòu)建的應(yīng)用的方法罢艾。
2.單元測試不要使用Dagger
如果想要寫個小的單元測試來測試@Inject
注解的類楣颠,其實不需要使用Dagger。僅需調(diào)用@Inject
注解的構(gòu)造方法咐蚯、設(shè)置@Inject
注解的屬性和調(diào)用需測試的方法童漩,如果可以,直接傳遞假的或模擬的依賴項春锋。
final class ThingDoer {
private final ThingGetter getter;
private final ThingPutter putter;
@Inject ThingDoer(ThingGetter getter, ThingPutter putter) {
this.getter = getter;
this.putter = putter;
}
String doTheThing(int howManyTimes) { /* … */ }
}
public class ThingDoerTest {
@Test
public void testDoTheThing() {
ThingDoer doer = new ThingDoer(fakeGetter, fakePutter);
assertEquals("done", doer.doTheThing(5));
}
}
3.替換依賴數(shù)據(jù)
功能睁冬、集成、端到端測試通常用于產(chǎn)線應(yīng)用看疙,用假的(在大型功能測試中不使用模擬的)數(shù)據(jù)替換持久化豆拨、后端和認證系統(tǒng)的數(shù)據(jù),使應(yīng)用的剩余部分能正常工作能庆。這種方法在測試配置替換產(chǎn)品配置中的一些數(shù)據(jù)時施禾,有助于掌控一個(也許少量的)測試配置項。
選項1:通過子類Module重寫依賴項(不建議)
在測試Component中搁胆,替換依賴項最簡單的辦法就是通過子類重寫Module里@Provides
注解的方法弥搞。(后面會講到存在的問題。)當創(chuàng)建Component的實例渠旁,傳入它需使用的Module對象攀例。(可以但不需要傳入這樣的Module對象,有無參構(gòu)造方法或都是靜態(tài)方法 顾腊。)這意味著可以傳入那些Module子類的對象粤铭,而且那些子類可以重寫一些@Provides
注解的方法來替換依賴項。
@Component(modules = {AuthModule.class, /* … */})
interface MyApplicationComponent { /* … */ }
@Module
class AuthModule {
@Provides AuthManager authManager(AuthManagerImpl impl) {
return impl;
}
}
class FakeAuthModule extends AuthModule {
@Override
AuthManager authManager(AuthManagerImpl impl) {
return new FakeAuthManager();
}
}
MyApplicationComponent testingComponent = DaggerMyApplicationComponent.builder()
.authModule(new FakeAuthModule())
.build();
但這種方法有些局限性:
第一杂靶,使用Module的子類不能改變依賴圖內(nèi)的關(guān)系:不能增加梆惯、刪除或更改依賴。尤其是:
- 重寫
@Provides
注解的方法不能更改它參數(shù)類型吗垮,且縮小范圍的返回類型對Dagger而言并不影響依賴圖垛吗。在上面的例子中,testingComponent對象需要的仍然是AuthManagerImpl和它相關(guān)的依賴烁登,即使它們沒有被使用怯屉。 - 同樣的,重寫Module不能給依賴圖增加關(guān)系,包括新的多元綁定(即使仍然能重寫
SET_VALUES
方法返回不同的Set)锨络。子類中任何新的@Provides
注解的方法都默認被Dagger忽略赌躺。實際上,可理解為假的依賴項欺騙不了依賴注入足删。
第二寿谴,這種方式下,可重寫的@Provides
注解的方法不可能是靜態(tài)的失受,所以它們Module對象不能被忽略讶泰。
選項2:分開配置Component
另一種方法要求應(yīng)用中有更多預(yù)設(shè)的Module。產(chǎn)線應(yīng)用中的每個配置拂到,都得在測試Component中進行不同的配置痪署。測試Component類繼承自產(chǎn)線Component類,而且添加一系列不同的Module兄旬。
@Component(modules = {
OAuthModule.class, // real auth
FooServiceModule.class, // real backend
OtherApplicationModule.class,
/* … */ })
interface ProductionComponent {
Server server();
}
@Component(modules = {
FakeAuthModule.class, // fake auth
FakeFooServiceModule.class, // fake backend
OtherApplicationModule.class,
/* … */})
interface TestComponent extends ProductionComponent {
FakeAuthManager fakeAuthManager();
FakeFooService fakeFooService();
}
測試時狼犯,調(diào)用
DaggerTestComponent.builder()
取代DaggerProductionComponent.builder()
作為Main方法。注意领铐,測試Component接口可以增加預(yù)定的對假數(shù)據(jù)的處理(fakeAuthManager()
和fakeFooService()
)悯森,那樣必要情況下,可在測試中訪問它們來掌控數(shù)據(jù)绪撵。
下面來講一講如何設(shè)計Module來簡化這個模式瓢姻。
4.可測試的模塊設(shè)計
Module類是一種工具類:包含單獨的@Provides
注解的方法的集合,里面每個方法都可能被用來給應(yīng)用注入需要的一些類型音诈。(雖然幾個@Provides
注解的方法可能相關(guān)聯(lián)幻碱,一個依賴另一個提供的類型,它們通常不會顯示調(diào)用彼此或依賴相同的可變狀態(tài)细溅。一些@Provides
注解的方法引用相同的屬性對象褥傍,這樣的話它們實際并不獨立。這里給點建議喇聊,無論如何要像對待工具方法一樣對待@Provides
注解的方法恍风,因為它使Module在測試時更容易被替換。)
那么如何決定哪些@Provides
注解的方法應(yīng)該放在一個Module類中承疲?
一方面考慮到將依賴劃分為公開的和內(nèi)部的邻耕,然后進一步考慮公開的依賴是否有合理的替代方案。
- 公開的依賴是那些提供功能的燕鸽、被應(yīng)用其它部分使用的。像AuthManager或User或DocDatabase這些類型是公開的:在Module中聲明啼辣,應(yīng)用其它部分可以使用它們啊研。
- 內(nèi)部的依賴是除公開依賴之外的:被用來實現(xiàn)一些公開的類型,除了作為它的一部分,并不一定要被使用党远。舉個例子削解,配置認證客戶端ID或OAuthKeyStore的依賴打算只在AuthManager實現(xiàn)認證的時候使用,而不是應(yīng)用的其它部分沟娱。這些依賴通常是包內(nèi)私有類型或被包內(nèi)私有限定符修飾氛驮。
這些公開的依賴將有合理的替代方案,主要用于測試济似,其它情況則不用矫废。舉個例子,像AuthManager這類型的替代依賴項:一個用于測試砰蠢,其它用于不同的認證/授權(quán)協(xié)議蓖扑。
另一方面,如果AuthManager接口有個方法返回當前登錄的用戶台舱,可能想要簡單調(diào)用AuthManager的getCurrentUser()
方法提供User的公開依賴律杠。這種公開的依賴不太可能需要替代方案。
一旦劃分為帶合理替代方案的公開依賴竞惋、不帶合理替代方案的公開依賴和內(nèi)部依賴柜去,可以考慮這樣安排它們到Module中:
- 為每個帶合理替代方案的公開依賴提供Module。這Module顯示包含一個公開的依賴拆宛,以及它需要的所有的內(nèi)部依賴嗓奢。
- 所有不帶合理替代方案的公開依賴按照功能的順序放入Module中。
- 每個公開依賴的Module應(yīng)該包含需被提供公開依賴的不帶合理替代方案的模塊胰挑。
通過描述提供的公開依賴來記錄每個Module是個好的主意蔓罚。這有個認證相關(guān)的例子。有個AuthManager
接口及兩個實現(xiàn)瞻颂,一個實現(xiàn)有認證邏輯豺谈,另一個假的實現(xiàn)用于測試。產(chǎn)線配置將使用真實的Module贡这,而測試配置假的Module茬末。同上,還有個不期望隨著配置改變的關(guān)于當前用戶的顯式依賴盖矫。
/**
* Provides auth bindings that will not change in different auth configurations,
* such as the current user.
*/
@Module
class AuthModule {
@Provides static User currentUser(AuthManager authManager) {
return authManager.currentUser();
}
// Other bindings that don’t differ among AuthManager implementations.
}
/** Provides a {@link AuthManager} that uses OAuth. */
@Module(includes = AuthModule.class) // Include no-alternative bindings.
class OAuthModule {
@Provides static AuthManager authManager(OAuthManager authManager) {
return authManager;
}
// Other bindings used only by OAuthManager.
}
/** Provides a fake {@link AuthManager} for testing. */
@Module(includes = AuthModule.class) // Include no-alternative bindings.
class FakeAuthModule {
@Provides static AuthManager authManager(FakeAuthManager authManager) {
return authManager;
}
// Other bindings used only by FakeAuthManager.
}
5.總結(jié)
關(guān)于Dagger 2的常見使用丽惭,到此算是翻譯結(jié)束了。通過這段時間的學(xué)習(xí)辈双,覺得Dagger 2對于代碼的拆解和封裝有很大的幫助责掏,可以大大簡化代碼,突出體現(xiàn)業(yè)務(wù)邏輯湃望,降低了應(yīng)用的耦合性换衬。歡迎大家在使用的同時痰驱,將心得體會與我交流,在此感謝瞳浦!