翻译Dagger 2与测试

作者: lanceJin | 来源:发表于2017-10-15 17:52 被阅读32次

    官方文档链接: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对于代码的拆解和封装有很大的帮助,可以大大简化代码,突出体现业务逻辑,降低了应用的耦合性。欢迎大家在使用的同时,将心得体会与我交流,在此感谢!

    相关文章

      网友评论

        本文标题:翻译Dagger 2与测试

        本文链接:https://www.haomeiwen.com/subject/izucuxtx.html