【问题标题】:How does Dagger 2 make testing easier on Android?Dagger 2 如何让 Android 上的测试更轻松?
【发布时间】:2015-12-05 21:31:06
【问题描述】:

使用 DI 的最大优势之一是它使测试变得更加容易(What is dependency injection? 也支持它)。我在其他编程语言上使用过的大多数 DI 框架(.NET 上的MEFObj-C/Swift 上的TyphoonLaravel's PHP 上的 IoC Container 和其他一些)允许开发人员在每个组件的单个入口点上注册依赖项,从而防止“创建”对对象本身的依赖项。

阅读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 所依赖的网络服务类?我希望答案尽可能简单,因为我只对测试感兴趣。

【问题讨论】:

    标签: android unit-testing dependency-injection dagger-2


    【解决方案1】:

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

    ...除了鼓励您首先注入依赖项之外,这自然会使各个类更具可测试性。

    我最后一次听说,Dagger 2 团队仍在考虑改进对测试的支持的潜在方法 - 尽管无论正在进行什么讨论,它们似乎都不是很公开。

    那么我现在该如何测试呢?

    您正确地指出想要显式使用组件的类对它有依赖关系。所以...注入那个依赖!你必须“手动”注入组件,但这应该不会太麻烦。

    官方方式

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

    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();
    

    这种方法适用于您在运行测试时总是想要替换的东西 - 例如。您希望在模拟服务器上运行的网络代码,或用于运行 Espresso 测试的 IdlingResource 实现。

    非官方方式

    不幸的是,官方方式可能涉及大量样板代码 - 一次性的很好,但如果您只想为一组特定的测试换出单个依赖项,那就真的很痛苦了。

    我最喜欢的 hack 是简单地扩展具有您想要替换的依赖项的任何模块,然后覆盖 @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();
    

    查看this gist 获取完整示例。

    【讨论】:

    • 我仍然很惊讶,只要您不在模拟方法上指定 @Provides 注释,您实际上就可以覆盖模块。
    【解决方案2】:

    "允许开发人员在单个入口点注册依赖项 每个组件” - Dagger 2 中的类似物是 Modules 和 Components,您可以在其中定义依赖项。优点是您无需直接在组件中定义依赖项,因此稍后将其解耦编写单元测试,您可以将 Dagger 2 component 切换为测试测试。

    “整个“无反射”业务听起来很棒” - “无反射”并不是关于匕首的“大问题”。 “大问题”是编译时的完整依赖图验证。其他 DI 框架没有此功能,如果您未能定义如何满足某些依赖项,您将在运行时后期收到错误。如果错误位于一些很少使用的代码路径中,您的程序可能看起来是正确的,但它会在将来的某个时候失败。

    “即使您没有显式调用 new,您仍然必须创建依赖项。” - 好吧,您总是必须以某种方式启动依赖项注入。其他 DI 可能会“隐藏”/自动化此活动,但最终会在某个地方执行图表构建。对于匕首 1 和 2,这是在应用程序启动时完成的。对于 main() 中的“普通”应用程序(如示例中所示),对于 android 应用程序 - 在 Application 类中。

    “您可以清楚地看到 DI 组件与实际代码之间没有解耦” - 是的,您 100% 正确。这是因为您不能直接控制 Android 中的活动、片段和服务的生命周期,即操作系统为您创建这些对象,而操作系统不知道您正在使用 DI。您需要手动注入您的活动、片段和服务。起初这似乎很尴尬,但在现实生活中,唯一的问题是有时您可能会忘记在 onCreate() 中注入您的活动并在运行时获取 NPE。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-10
      • 2010-09-17
      • 1970-01-01
      • 2019-08-27
      • 2021-10-16
      • 2012-07-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多