[译]使用 Dagger,Robolectric 和 InStrumentation 在单元测试,集成测试与功能测试中模拟依赖

原文:How to mock dependencies in Unit, Integration and Functional tests; Dagger, Robolectric and Instrumentation

作者:Artem Zinnatullin

译者:lovexiaov

2015-10-28-09-02-23-1-copy.jpg

亲爱的读者,你好。

最初,这只是一个对 Reddit 上 Robolectric and Dagger 2 的评论。但是由于内容很多所以我决定将它总结为一篇博客,祝您阅读愉快。


依赖注入(DI)框架 & 单元测试

单元测试中,我们经常隔离测试一个类或方法。如果被测试的是有行为的类,如RestApiDataManager等,你需要模拟它的依赖;如果被依赖的是“值类”,如UserMessage等,你可以直接使用它们(也可以模拟它们)。

这意味着通常我们并不需要在单元测试中使用 DI 框架,因为单元测试目标只是一个类或一个方法,而不是几个类。目标类应该通过以下方式获取依赖:

  • 通过构造方法(推荐方式)
  • 通过方法或字段

99% 的单元测试都不需要 DI 框架,通常只有像ActivityFragmentViewService这样在创建以后需要一系列依赖的类需要与 DI 框架交互。然而,我提倡使用 MVP 等模式将逻辑从 Android 框架类中移出,并且使用功能(UI)测试而不是单元测试来覆盖它们。

DI 框架 & 集成测试

一般而言,你也不必在集成测试中使用 DI 框架。因为集成测试只是简单的将几个类关联起来测试它们的集成。如果你的代码是依赖注入友好的(DI-friendly),你可以不使用 DI 框架将需要的依赖传入。

如果你确实需要通过 DI 框架提供模拟依赖,并且你使用了 Robolectric,请继续往下看。

DI 框架 & 功能(UI)测试

此类测试确实需要使用 DI 框架模拟依赖。因为一般来说,功能测试针对的是整个应用,而不是几个类。

如果你需要在instrumentation测试(Espresso,Robotium,或单纯的 instrumentation 测试等)中使用 DI 框架,请继续往下看。

如何使用 Dagger 2 和 Robolectric 在测试中模拟并注入依赖?

(通常适用于集成测试)

主要思路:对于 Roboletric 测试,可以自定义一个Application类,在其中模拟依赖。

你可以在 application 类中定义一个返回DaggerAppComponent的内部类Builder类对象,然后在集成测试时使用使用适当的子类覆盖该 application 类!

application 类

  public class MyApp extends Application {

  @NonNull // Initialized in onCreate.
  AppCompontent appComponent;

  @Override
  public void onCreate() {
    appComponent = prepareAppComponent().build();
  }

  // Here is the trick, we allow extend application class and modify AppComponent.
  @NonNull
  protected DaggerAppComponent.Builder prepareAppComponent() {
    return new DaggerAppComponent.Builder();
  }
}

用于集成测试的 application 类

public class MyIntegrationTestApp extends MyApp {

  @Override
  @NonNull
  protected DaggerAppComponent.Builder prepareAppComponent() {
    return super.prepareAppComponent()
      .someModule(new SomeModule() {
        @Override
        public SomeDependency provideSomeDependency(@NonNull SomeArgs someArgs) {
          return mock(SomeDependency.class); // You can provide any kind of mock you need.
        }
      })
  }
}

然后通过自定义RobolectricGradleTestRunner类使用该类。

自定义带有定制化 application 类的 Robolectric 测试执行器

public class IntegrationRobolectricTestRunner extends RobolectricGradleTestRunner {

    // This value should be changed as soon as Robolectric will support newer api.
    private static final int SDK_EMULATE_LEVEL = 21;

    public IntegrationRobolectricTestRunner(@NonNull Class<?> clazz) throws Exception {
        super(clazz);
    }

    @Override
    public Config getConfig(@NonNull Method method) {
        final Config defaultConfig = super.getConfig(method);
        return new Config.Implementation(
                new int[]{SDK_EMULATE_LEVEL},
                defaultConfig.manifest(),
                defaultConfig.qualifiers(),
                defaultConfig.packageName(),
                defaultConfig.resourceDir(),
                defaultConfig.assetDir(),
                defaultConfig.shadows(),
                MyIntegrationTestApp.class, // Here is the trick, we change application class to one with mocks.
                defaultConfig.libraries(),
                defaultConfig.constants() == Void.class ? BuildConfig.class : defaultConfig.constants()
        );
    }
}

如何在 Instrumentation 测试中使用 Dagger 2 模拟并注入依赖?

Google 的那帮兄弟建议使用 flavors,但我并不推荐使用它。因为 flavors 越多,构建应用花费的时间越长,你也会越不喜欢 Gradle 以及整个构建过程。如果可以避免,尽量不要使用 flavors。

主要思路:与 Roboletric 测试相似——通过修改Application类来实现,不过这次是针对 Instrumentation 测试。

为了实现此方法,你需要自定义 Instrumentation 测试执行器,并将它们添加到build.gradle中。

public class CustomInstrumentationTestRunner extends AndroidJUnitRunner {

  @Override
  @NonNull
  public Application newApplication(@NonNull ClassLoader cl, 
                                    @NonNull String className, 
                                    @NonNull Context context)
                                    throws InstantiationException, 
                                    IllegalAccessException, 
                                    ClassNotFoundException {
    return Instrumentation.newApplication(CustomApp.class, context);
  }
}

将该类添加到build.gradle中:

android {  
  defaultConfig {
    testInstrumentationRunner 'a.b.c.CustomInstrumentationTestRunner'
  }
}

代码示例:我已经将内容更新到 #qualitymatters app中,并做了分析,展示了在单元测试,集成测试和功能测试中模拟依赖的方式。 因为我们在测试真正应用时需要分析,但在实验中却不想分析!也许你已经注意到了,我们没有添加 flavors。

你可以看一下提交请求详情

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • @Author:彭海波 前言 单元测试(又称为模块测试, Unit Testing)是针对程序模块(软件设计的最小...
    海波笔记阅读 4,946评论 0 52
  • 标签(空格分隔): Android 单元测试的好处:Martin Fowler在《重构》里面还解释了为什么单元测试...
    背影杀手不太冷阅读 5,804评论 3 25
  • 这张牌和这场电影,是一场华丽的相遇。 心中有爱和慈悲,凶恶的狼也能感受到这博大的爱。 整个影片,我感受到了强烈的纯...
    一闪_31de阅读 351评论 0 0
  • 讲述者/秦瑛 图片/来自网络 1/ 今天很意外地接到杨坤的电话。其实也不能说意外吧,因为他总是这样,在我好不容易不...
    老黄说史阅读 548评论 3 7