翻译Dagger 2与测试

官方文档链接:https://google.github.io/dagger/testing.html

1.前言


官网上还有篇关于Java中异步地依赖注入的文章,由于得引入Guava包,感觉Android上不太常用,所以没有翻译。若后期项目需要,会再来翻译的。

使用像Dagger之类的依赖注入框架的好处之一,是它让代码测试更简单。下面探讨一些测试Dagger构建的应用的方法。

2.单元测试不要使用Dagger


如果想要写个小的单元测试来测试@Inject注解的类,其实不需要使用Dagger。仅需调用@Inject注解的构造方法、设置@Inject注解的属性和调用需测试的方法,如果可以,直接传递假的模拟的依赖项。

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.替换依赖数据


功能、集成、端到端测试通常用于产线应用,用假的(在大型功能测试中不使用模拟的)数据替换持久化、后端和认证系统的数据,使应用的剩余部分能正常工作。这种方法在测试配置替换产品配置中的一些数据时,有助于掌控一个(也许少量的)测试配置项

选项1:通过子类Module重写依赖项(不建议)

在测试Component中,替换依赖项最简单的办法就是通过子类重写Module里@Provides注解的方法。(后面会讲到存在的问题。)当创建Component的实例,传入它需使用的Module对象。(可以但不需要传入这样的Module对象,有无参构造方法都是静态方法 。)这意味着可以传入那些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的子类不能改变依赖图内的关系:不能增加、删除或更改依赖。尤其是:

  • 重写@Provides注解的方法不能更改它参数类型,且缩小范围的返回类型对Dagger而言并不影响依赖图。在上面的例子中,testingComponent对象需要的仍然是AuthManagerImpl和它相关的依赖,即使它们没有被使用。
  • 同样的,重写Module不能给依赖图增加关系,包括新的多元绑定(即使仍然能重写SET_VALUES方法返回不同的Set)。子类中任何新的@Provides注解的方法都默认被Dagger忽略。实际上,可理解为假的依赖项欺骗不了依赖注入。

第二,这种方式下,可重写的@Provides注解的方法不可能是静态的,所以它们Module对象不能被忽略。

选项2:分开配置Component

另一种方法要求应用中有更多预设的Module。产线应用中的每个配置,都得在测试Component中进行不同的配置。测试Component类继承自产线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();
}

测试时,调用DaggerTestComponent.builder()取代DaggerProductionComponent.builder()作为Main方法。注意,测试Component接口可以增加预定的对假数据的处理(fakeAuthManager()fakeFooService()),那样必要情况下,可在测试中访问它们来掌控数据。

下面来讲一讲如何设计Module来简化这个模式。

4.可测试的模块设计


Module类是一种工具类:包含单独的@Provides注解的方法的集合,里面每个方法都可能被用来给应用注入需要的一些类型。(虽然几个@Provides注解的方法可能相关联,一个依赖另一个提供的类型,它们通常不会显示调用彼此依赖相同的可变状态。一些@Provides注解的方法引用相同的属性对象,这样的话它们实际并不独立。这里给点建议,无论如何要像对待工具方法一样对待@Provides注解的方法,因为它使Module在测试时更容易被替换。)

那么如何决定哪些@Provides注解的方法应该放在一个Module类中?

一方面考虑到将依赖划分为公开的和内部的,然后进一步考虑公开的依赖是否有合理的替代方案。

  • 公开的依赖是那些提供功能的、被应用其它部分使用的。像AuthManager或User或DocDatabase这些类型是公开的:在Module中声明,应用其它部分可以使用它们。
  • 内部的依赖是除公开依赖之外的:被用来实现一些公开的类型,除了作为它的一部分,并不一定要被使用。举个例子,配置认证客户端ID或OAuthKeyStore的依赖打算只在AuthManager实现认证的时候使用,而不是应用的其它部分。这些依赖通常是包内私有类型或被包内私有限定符修饰

这些公开的依赖将有合理的替代方案,主要用于测试,其它情况则不用。举个例子,像AuthManager这类型的替代依赖项:一个用于测试,其它用于不同的认证/授权协议。

另一方面,如果AuthManager接口有个方法返回当前登录的用户,可能想要简单调用AuthManager的getCurrentUser()方法提供User的公开依赖。这种公开的依赖不太可能需要替代方案。

一旦划分为带合理替代方案的公开依赖、不带合理替代方案的公开依赖和内部依赖,可以考虑这样安排它们到Module中:

  • 为每个带合理替代方案的公开依赖提供Module。这Module显示包含一个公开的依赖,以及它需要的所有的内部依赖。
  • 所有不带合理替代方案的公开依赖按照功能的顺序放入Module中。
  • 每个公开依赖的Module应该包含需被提供公开依赖的不带合理替代方案的模块。

通过描述提供的公开依赖来记录每个Module是个好的主意。这有个认证相关的例子。有个AuthManager接口及两个实现,一个实现有认证逻辑,另一个假的实现用于测试。产线配置将使用真实的Module,而测试配置假的Module。同上,还有个不期望随着配置改变的关于当前用户的显式依赖。

/**
 * 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.总结


关于Dagger 2的常见使用,到此算是翻译结束了。通过这段时间的学习,觉得Dagger 2对于代码的拆解和封装有很大的帮助,可以大大简化代码,突出体现业务逻辑,降低了应用的耦合性。欢迎大家在使用的同时,将心得体会与我交流,在此感谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352