Android Architecture ComponentsAndroid开发经验谈Android开发

翻译Dagger 2与测试

2017-10-15  本文已影响32人  lanceJin

官方文档链接: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注解的方法不可能是静态的,所以它们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这类型的替代依赖项:一个用于测试,其它用于不同的认证/授权协议。

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

一旦划分为带合理替代方案的公开依赖、不带合理替代方案的公开依赖和内部依赖,可以考虑这样安排它们到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对于代码的拆解和封装有很大的帮助,可以大大简化代码,突出体现业务逻辑,降低了应用的耦合性。欢迎大家在使用的同时,将心得体会与我交流,在此感谢!

上一篇下一篇

猜你喜欢

热点阅读