简体   繁体   English

Dagger 2如何在Android上更轻松地进行测试?

[英]How does Dagger 2 make testing easier on Android?

One of the best advantages of using DI is it makes testing a lot easier ( What is dependency injection? backs it too). 使用DI的最大好处之一是它使测试变得更容易( 什么是依赖注入?也支持它)。 Most of DI frameworks I've worked with on other programming languages ( MEF on .NET , Typhoon on Obj-C/Swift , Laravel 's IoC Container on PHP , and some others) allows the developer do register dependencies on a single entry point for each component, thus preventing the "creation" of dependency on the object itself. 大多数DI框架,我与其他编程语言(工作的MEF.NET, 台风的OBJ-C /斯威夫特Laravel的IoC容器上的PHP,和其他一些),允许开发人员做一个单一的入口点寄存器相关对于每个组件,从而防止“创建”对象本身的依赖。

After I read Dagger 2 documentation, it sounds great the whole "no reflection" business, but I fail to see how it makes testing easier as objects are still kind of creating their own dependencies. 在我阅读Dagger 2文档之后,整个“无反思”的业务听起来很棒,但我没有看到它如何使测试变得更容易,因为对象仍然在创建自己的依赖项。

For instance, in the CoffeMaker example: 例如,在CoffeMaker示例中:

public class CoffeeApp {
  public static void main(String[] args) {

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

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

Even though you're not explicitly calling new , you still have to create your dependency. 即使你没有明确地调用new ,你仍然需要创建你的依赖。

Now for a more detailed example, let's go to an Android Example . 现在,有关更详细的示例,请转到Android示例 If you open up DemoActivity class, you will notice the onCreate implementation goes like this: 如果你打开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);
}

You can clearly see there is no decoupling from the DI component, to the actual code. 您可以清楚地看到DI组件与实际代码没有脱钩。 In summary, you'd need to mock/stub ((DemoApplication) getApplication()).component().inject(this); 总之,你需要mock / stub ((DemoApplication) getApplication()).component().inject(this); on a test case (if that's even possible). 在测试用例上(如果可能的话)。

Up to this point, I am aware Dagger 2 is pretty popular, so there is got to be something I am not seeing. 到目前为止,我知道Dagger 2很受欢迎,所以我不会看到它。 So how does Dagger 2 makes testing classes easier? 那么Dagger 2如何让测试课更容易? How would I mock, let's say a network service class that my Activity depends on? 我将如何模拟,假设我的活动所依赖的网络服务类? I would like the answer to be as simple as possible as I'm only interested in testing. 我希望答案尽可能简单,因为我只对测试感兴趣。

Dagger 2 doesn't make testing easier Dagger 2不会使测试更容易

...beyond encouraging you to inject dependencies in the first place, which naturally makes individual classes more testable. ...除了鼓励你首先注入依赖关系之外,这自然会使个别类更容易测试。

The last I heard, the Dagger 2 team were still considering potential approaches to improving support for testing - though whatever discussions are going on, they don't seem to be very public. 最后我听说,Dagger 2团队仍在考虑改进测试支持的潜在方法 - 尽管正在进行任何讨论,但它们似乎并不公开。

So how do I test now? 那么我该如何测试呢?

You're correct to point out that classes which want to make explicit use of a Component have a dependency on it. 你指出想要明确使用Component的类依赖于它是正确的。 So... inject that dependency! 所以... 注入依赖! You'll have to inject the Component 'by hand', but that shouldn't be too much trouble. 你必须“手动”注入组件,但这不应该太麻烦。

The official way 官方的方式

Currently, the officially-recommended approach to swapping dependencies for testing is to create a test Component which extends your production one, then have that use custom modules where necessary. 目前,官方推荐的交换依赖项进行测试的方法是创建一个测试组件,扩展您的生产,然后在必要时使用自定义模块。 Something like this: 像这样的东西:

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

This approach works well for the things you always want to replace when you are running tests - eg Networking code where you want to run against a mock server instead, or IdlingResource implementations of things for running Espresso tests. 这种方法适用于您在运行测试时总是想要替换的内容 - 例如,您希望针对模拟服务器运行的网络代码,或者用于运行Espresso测试的IdlingResource实现。

The unofficial way 非正式的方式

Unfortunately, it the official way can involve a lot of boilerplate code - fine as a one-off, but a real pain if you only want to swap out a single dependency for one particular set of tests. 不幸的是,它的官方方式可能涉及很多样板代码 - 很好的一次性,但如果你只想换掉一个特定测试集的单个依赖项,真的很痛苦。

My favourite hack for this is to simply extend whichever Module has the dependency you want to replace, then override the @Provides method. 我最喜欢的黑客是简单地扩展哪个Module有你想要替换的依赖项,然后覆盖@Provides方法。 Like so: 像这样:

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

Check this gist for a full example. 请查看此要点以获取完整示例。

"allows the developer do register dependencies on a single entry point for each component" - analogues in Dagger 2 are the Module s and Component s where you define the dependencies. “允许开发人员在每个组件的单个入口点上注册依赖项” - Dagger 2中的类似物是ModuleComponent ,您可以在其中定义依赖项。 The advantage is that you don't define the dependencies directly in your component thus decoupling it so later when writing unit tests you may switch the Dagger 2 component with a test one. 优点是您不直接在组件中定义依赖项,从而将其解耦,以便稍后在编写单元测试时,您可以使用测试component切换Dagger 2 component

"it sounds great the whole "no reflection" business" - the "no reflection" thing is not the "big deal" about dagger. “听起来很棒”整个“没有反思”的事业“ - ”没有反思“的事情并不是关于匕首的”大不了“。 The "big deal" is the full dependency graph validation at compile time. “大不了”是编译时的完整依赖图验证。 Others DI frameworks don't have this feature and if you fail to define how some dependency is satisfied you will get an error late at runtime. 其他DI框架没有此功能,如果您无法定义如何满足某些依赖项,您将在运行时迟到时收到错误。 If the error is located in some rarely used codepath your program may look like it is correct but it will fail at some point in the future. 如果错误位于一些很少使用的代码路径中,那么您的程序可能看起来是正确的,但在将来某个时候它会失败。

"Even though you're not explicitly calling new, you still have to create your dependency." “即使你没有明确地调用new,你仍然需要创建你的依赖。” - well, you always have to somehow initiate dependency injection. - 好吧,你总是要以某种方式启动依赖注入。 Other DI may "hide"/automate this activity but at the end somewhere building of the graph is performed. 其他DI可以“隐藏”/自动化此活动,但最后在某处构建图形。 For dagger 1&2 this is done at app start. 对于匕首1和2,这是在应用程序启动时完成的。 For "normal" apps (as you shown in the example) in the main() , For android apps - in the Application class. 对于main()中的“普通”应用程序(如示例中所示main() ,对于Android应用程序 - 在Application类中。

"You can clearly see there is no decoupling from the DI component, to the actual code" - Yes, you are 100% correct. “你可以清楚地看到DI组件与实际代码没有脱钩” - 是的,你是100%正确的。 That arises from the fact that you don't control directly the lifecycle of the activities, fragments and services in Android, ie the OS creates these objects for you and the OS is not aware that you are using DI. 这是因为您不直接控制Android中活动,片段和服务的生命周期,即操作系统为您创建这些对象,并且操作系统不知道您正在使用DI。 You need manually to inject your activities, fragments and services. 您需要手动注入活动,片段和服务。 At first this seem seems awkward but in real life the only problem is that sometimes you may forget to inject your activity in onCreate() and get NPE at runtime. 起初这似乎很尴尬,但在现实生活中唯一的问题是,有时你可能忘记在onCreate()注入你的活动并在运行时获得NPE。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM