Dagger 2如何让Android测试更容易?

6
使用DI的最大优点之一是使测试变得更加容易(什么是依赖注入? 也支持这个观点)。我曾经在其他编程语言上使用过大多数DI框架(.NET上的MEFObj-C/Swift上的TyphoonPHP上的Laravel IoC容器等),它们允许开发者为每个组件在单个入口点注册依赖项,从而防止对象本身“创建”依赖项。
在我阅读Dagger 2文档后,整个“无反射”业务听起来很棒,但我无法看到它如何使测试变得更容易,因为对象仍然会自己创建依赖项。
例如,在CoffeMaker示例中:
public class CoffeeApp {
  public static void main(String[] args) {

    // THIS LINE
    CoffeeShop coffeeShop = DaggerCoffeeShop.create();

    coffeeShop.maker().brew();
  } 
}

即使您没有显式调用 new,您仍然必须创建您的依赖项。
现在让我们来看一个更详细的例子,转到 Android Example。 如果您打开 DemoActivity 类,您会注意到 onCreate 实现如下:
@Override 
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
    // Perform injection so that when this call returns all dependencies will be available for use.
   ((DemoApplication) getApplication()).component().inject(this);
}

您可以清楚地看到,从 DI 组件到实际代码并没有解耦。简而言之,在测试用例中需要模拟/存根 ((DemoApplication) getApplication()).component().inject(this);(如果可能的话)。

到目前为止,我知道 Dagger 2 很受欢迎,所以肯定有我没有注意到的东西。那么 Dagger 2 如何使测试类更容易?我将如何模拟,比方说一个与我的 Activity 相关联的网络服务类? 我希望答案尽可能简单,因为我只关心测试。

2个回答

9

Dagger 2不会让测试变得更容易

...除了鼓励你首先注入依赖项,这自然使单个类更具可测试性。

据我所知,Dagger 2团队仍在考虑改进测试支持的潜在方法-尽管无论正在进行哪些讨论,它们似乎并不是非常公开的。

那么现在我该如何测试?

你指出想要明确使用Component的类对其具有依赖关系。所以...注入该依赖关系!您将不得不手动注入组件,但这不应该太麻烦。

官方方式

目前,推荐用于交换测试依赖项的官方方法是创建一个测试Component,该组件扩展您的生产组件,然后在必要时使用自定义模块。像这样:

public class CoffeeApp {
  public static CoffeeShop sCoffeeShop;

  public static void main(String[] args) {
    if (sCoffeeShop == null) {
      sCoffeeShop = DaggerCoffeeShop.create();
    }

    coffeeShop.maker().brew();
  } 
}

// Then, in your test code you inject your test Component.
CoffeeApp.sCoffeeShop = DaggerTestCoffeeShop.create();

这种方法适用于在运行测试时始终要替换的内容,例如网络代码,您希望使用模拟服务器,或者 IdlingResource 实现,用于运行 Espresso 测试。

非官方方法

不幸的是,官方方法可能涉及大量样板代码,一次性可以接受,但如果您只想为一个特定的测试集替换单个依赖项,则会非常麻烦。
我最喜欢的解决方法是简单地扩展包含您想要替换的依赖项的任何模块,然后覆盖 @Provides 方法。就像这样:
CoffeeApp.sCoffeeShop = DaggerCoffeeShop.builder()
    .networkModule(new NetworkModule() {
        // Do not add any @Provides or @Scope annotations here or you'll get an error from Dagger at compile time.
        @Override
        public RequestFactory provideRequestFactory() {
          return new MockRequestFactory();
        }
    })
    .build();

请查看此代码片段以获得完整示例。


1
我仍然很惊讶,只要在模拟方法上不指定@Provides注释,就可以覆盖模块。 - EpicPandaForce

2
"allows the developer do register dependencies on a single entry point for each component" - 在Dagger 2中,相当于这个功能的是ModuleComponent。您可以在这里定义依赖项,优点是不需要直接在组件中定义依赖项,从而使其解耦,因此,在编写单元测试时,您可以将Dagger 2的component与测试组件进行切换。
"it sounds great the whole "no reflection" business" - Dagger的“无反射”并不是最重要的特性。最重要的是完整的依赖关系图在编译时进行验证。其他DI框架没有这个功能,如果您未能定义某个依赖项的满足方式,则会在运行时出现错误。如果错误位于某个很少使用的代码路径中,则您的程序可能看起来是正确的,但在将来的某个时间点会失败。
"Even though you're not explicitly calling new, you still have to create your dependency." - 好吧,您始终必须以某种方式启动依赖注入。其他DI可能会“隐藏”/自动化此活动,但最终会执行某些构建图形的操作。对于Dagger 1&2,这是在应用程序启动时完成的。对于“普通”应用程序(如示例所示),在main()中进行。对于Android应用程序,则在Application类中进行。
"You can clearly see there is no decoupling from the DI component, to the actual code" - 是的,您是100%正确的。这是因为您无法直接控制Android中活动、片段和服务的生命周期,即操作系统会为您创建这些对象,并且操作系统不知道您正在使用DI。您需要手动注入您的活动、片段和服务。起初,这似乎很奇怪,但实际上唯一的问题是有时您可能会忘记在onCreate()中注入您的活动并在运行时获得NPE。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接