在上一篇文章中,我们讲了要将mock出来的dependency真正使用起来,需要在测试环境下通过某种方式set 到用到它的那个对象里面进去,替换掉真实的实现。我们前面举的例子是:

public class LoginPresenter {private UserManager mUserManager = new UserManager();public void login(String username, String password) {//。。。some other codemUserManager.performLogin(username, password);}
}

在测试LoginPresenter#login()时,为了能够将mock出来的UserManager set到LoginPresenter里面,我们前面的做法是简单粗暴,给LoginPresenter加一个UserManager的setter。然而这种做法毕竟不是很优雅,一般来说,我们正式代码里面是不会去调用这个setter,修改UserManager这个对象的。因此这个setter存在的意义就纯粹是为了方便测试。这个虽然不是没有必要,却不是太好看,因此在有选择的情况下,我们不这么做。在这里,我们介绍依赖注入这种模式。

对于依赖注入(Dependency Injection,以下简称DI)的准确定义可以在这里找到。它的基本理念这边简单描述下,首先这是一种代码模式,这个模式里面有两个概念:Client和Dependency。假如你的代码里面,一个类用到了另外一个类,那么前者叫Client,后者叫Dependency。结合上面的例子,LoginPresenter用到了UserManager,那么LoginPresenter叫Client,UserManager叫Dependency。当然,这是个相对的概念,一个类可以是某个类的Dependency,却是另外一个类的Client。比如说如果UserManager里面用到了Retrofit,那么相对于RetrofitUserManager又是Dependency。DI的基本思想就是,对于Dependency的创建过程,并不在Client里面进行,而是由外部创建好,然后通过某种方式set到Client里面。这种模式,就叫做依赖注入。

是的,依赖注入就是这么简单的一个概念,这边需要澄清的一点是,这个概念本身跟dagger2啊,RoboGuice这些框架并没有什么关系。现在很多介绍DI的文章往往跟dagger2是在一起的,因为dagger2的使用相对来说不是很直观,所以导致很多人认为DI是多么复杂的东西,甚至认为只能用dagger等框架来实现依赖注入,其实不是这样的。实现依赖注入很简单,dagger这些框架只是让这种实现变得更加简单,简洁,优雅而已。

DI的常见实现方式

下面介绍DI的实现方式,通常来说,这里是大力介绍dagger2的地方。但是,虽然dagger2的确是非常好的东西,然而如果我直接介绍dagger2的话,会很容易导致一个误区,认为在测试的时候,也只能用dagger来做依赖注入或创建对应的测试类,因此,我这边刻意不介绍dagger。先让大家知道最基本的DI怎么实现,然后在测试的时候如何更方便高效的使用。

实现DI这种模式其实很简单,有多种方式,上一篇文章中提到的setter,其实就是实现DI的一种方式,叫做 setter injection 。此外,通过方法的参数传递进去(argument injection),也是实现DI的一种方式:

public class LoginPresenter {//这里,LoginPresenter不再持有UserManager的一个引用,而是作为方法参数直接传进去public void login(UserManager userManager, String username, String password) {//... some other codeuserManager.performLogin(username, password);}
}

然而更常用的方式,是将Dependency作为Client的构造方法的参数传递进去:

public class LoginPresenter {private final UserManager mUserManager;//将UserManager作为构造方法参数传进来public LoginPresenter(UserManager userManager) {this.mUserManager = userManager;}public void login(String username, String password) {//... some other codemUserManager.performLogin(username, password);}
}

这种实现DI的模式叫 Constructor Injection。其实一般来说,提到DI,指的都是这种方式。这种方式的好处是,依赖关系非常明显。你必须在创建这个类的时候,就提供必要的dependency。这从某种程度上来说,也是在说明这个类所完成的功能。因此,尽量使用 Constructor injection

说到这里,你可能会有一个疑问,如果把依赖都声明在Constructor的参数里面,这会不会让这个类的Constructor参数变得非常多?如果真的发生这种情况了,那往往说明这个类的设计是有问题的,需要重构。为什么呢?我们代码里面的类,一般可以分为两种,一种是Data类,比如说UserInfo,OrderInfo等等。另外一种是Service类,比如UserManager, AudioPlayer等等。所以这个问题就有两种情况了:

  1. 如果Constructor里面传入的很多是基本类型的数据或数据类,那么或许你要做的,是创建一个(或者是另一个)数据类把这些数据封装一下,这个过程的价值可是大大滴!而不仅仅是封装一下参数的问题,有了一个类,很多的方法就可以放到这个类里面了。这点请参考Martin Fowler的《重构》第十章“Introduce Parameter Object”。

  2. 如果传入的很多是service类,那么这说明这个类做的事情太多了,不符合单一职责的原则(Single Responsibility Principle,SRP),因此,需要重构。

接下来说回我们的初衷:DI在测试里面的应用。

DI在单元测试里面的应用

所谓DI在单元测试里面的应用,其实说白了就是使用DI模式,将mock出来的Dependency set到Client里面去。我相信这篇文章解释到这里,那么答案也就比较明显了,为了强调我们要尽量使用 Constructor injection,对于 setter InjectionArgument injection 这边就不做代码示例了。
如果你的代码使用的是 Constructor injection

public class LoginPresenter {private final UserManager mUserManager;//将UserManager作为构造方法参数传进来public LoginPresenter(UserManager userManager) {this.mUserManager = userManager;}public void login(String username, String password) {//... some other codemUserManager.performLogin(username, password);}
}

其中我们要测的方法是login(), 要验证login()方法调用了mUserManagerperformLigon()。对应的测试方法如下:

public class LoginPresenterTest {@Testpublic void testLogin() {UserManager mockUserManager = Mockito.mock(UserManager.class);LoginPresenter presenter = new LoginPresenter(mockUserManager);  //创建的时候,讲mock传进去presenter.login("xiaochuang", "xiaochuang password");Mockito.verify(mockUserManager).performLogin("xiaochuang", "xiaochuang password");}
}

很简单,对吧。

小结

这篇文章介绍了DI的概念,以及在单元测试里面的应用,这里特意没有介绍dagger2的使用,目的是要强调:

  1. 一个灵活的,易于测试的,符合SRP的,结构清晰的项目,关键在于要应用依赖注入这种模式,而不是用什么来做依赖注入。

  2. 等你学会使用dagger以后,要记得在测试的时候,如果可以直接mock dependency并传给被测类,那就直接创建,不是一定要使用dagger来做DI

然而如果完全不使用框架来做DI,那么在正式代码里面就有一个问题了,那就是dependency的创建工作就交给上层client去处理了,这可不是件好事情。想想看,LoginActivity里面创建LoginPresenter的时候,还得知道LoginPresenter用了UserManager。然后创建一个UserManager对象给LoginPresenter。对于LoginActivity来说,它觉得我才懒得管你用什么样的UserManager呢,我只想告诉你login的时候,你给我老老实实的login就好了,你用什么Manager我不管。所以,直接在LoginActivity里面创建UserManager,可能不是个好的选择。那怎么样算是一个好的选择呢?dagger2给了我们答案。
于是下一篇文章我们介绍dagger2。

文中的代码在github这个项目里面。

最后,如果你也对安卓单元测试感兴趣的话,欢迎加入我们的交流群:

作者 小创 更多文章 | Github | 公众号

Android单元测试(五):依赖注入,将mock方便的用起来相关推荐

  1. Android开发中依赖注入的应用

    什么是依赖注入? 依赖是指一个对象持有其他对象的引用.依赖注入则是将这些依赖对象传递给被依赖对象,而不是被依赖对象自己创建这些对象.  public class MyClass{private Ano ...

  2. java 单元测试_在springboot中写单元测试解决依赖注入和执行后事务回滚问题

    往期文章 「Java并发编程」谈谈Java中的内存模型JMM 面试官:说说你知道多少种线程池拒绝策略 为什么不要在MySQL中使用UTF-8编码方式 前言 很多公司都有写单元测试的硬性要求,在提交代码 ...

  3. springboot 事务嵌套问题_在springboot中写单元测试解决依赖注入和执行后事务回滚问题...

    往期文章 「Java并发编程」谈谈Java中的内存模型JMM 面试官:说说你知道多少种线程池拒绝策略 为什么不要在MySQL中使用UTF-8编码方式 前言 很多公司都有写单元测试的硬性要求,在提交代码 ...

  4. Android开源框架——依赖注入ButterKnife

    若对依赖注入不熟悉,请阅读博客中的另外一篇IOC控制反转浅析 介绍:ButterKnife是Square公司员工JakeWharton开发的一款针对View视图对象的依赖注入库.目的是通过依赖注入方式 ...

  5. SAP Spartacus OccEndpointsService单元测试的依赖注入

    OccEndpointsService的两个依赖: OccConfig和可选的BaseSiteService: OccConfig是SiteContextConfig的子类: export abstr ...

  6. Android开源框架——依赖注入Dagger

    介绍:Dagger是Square公司开发依赖注入框架,主要针对辅助类对象,而ButterKnife是针对View视图对象的. github:https://github.com/square/dagg ...

  7. 具有Hilt的Android上的依赖注入

    重点 (Top highlight) Dependency injection (DI) is a technique widely used in programming and well suit ...

  8. Android mock for循环,Android单元测试(五):依赖注入,将mock方便的用起来

    在上一篇文章中,咱们讲了要将mock出来的dependency真正使用起来,须要在测试环境下经过某种方式set 到用到它的那个对象里面进去,替换掉真实的实现.咱们前面举的例子是:html public ...

  9. [Android]使用Dagger 2依赖注入 - DI介绍(翻译)

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5092083.html 使用Dagger 2依赖注入 - DI介 ...

  10. Android 依赖注入可以更简单 —— 新版本 Dagger 2 使用教学

    今年 3 月 21 号 Dagger 2 在 2.10 版本之后针对 Android 方面做了很大的优化,使用方法也随之有了不少变化.本次改动除了让 Dagger 2 的使用更加符合控制反转原则,还针 ...

最新文章

  1. 一键清理 Nexus 中无用的 Docker 镜像
  2. FPGA的设计艺术(17)如何搭建一个简易的逻辑测试平台?
  3. mysql 四 表操作
  4. hust1346(两个线段的最近距离和最小距离)
  5. 怎么设置数据库服务器的最佳性能,调整Windows参数提高数据库服务器性能
  6. linux-2.6内核模块引用计数的实现(try_module_get和module_put)
  7. 模板设计模式_设计模式实用又简单的模板模式
  8. 小米盒子升级android tv后不能安装第三方软件下载,小米电视,小米盒子无法安装第三方应用,怎么办?...
  9. 如何修改iphone服务器,iPhone手机配置教程
  10. 【深度学习基础-02】概念学习-例子3则
  11. 前端页面设计 | 博客系统
  12. uc android快捷键,UC手机浏览器助力Android快速上网
  13. numpy部分函数使用总结
  14. MediaWiki 在 CentOS 下的安装使用
  15. Python如此神奇,让繁琐工作自动化 (文中含Python基础)
  16. 永远不要忽视 粉红色/红色的异样字体 在你不知道为什么跟你期望偏差那么大的时候,,不要急记得去问问为什么
  17. 数据结构4 Tree
  18. Ubuntu系统关闭搜狗输入法Shift切换中英文
  19. 川教版计算机三年级下册教案,三年级下册川教版信息技术教案
  20. 毫无破绽的:Access denied for user ‘root‘@‘%‘ (using password: YES)

热门文章

  1. 以服务器时间为基准显示到某一时间的倒计时
  2. 传输层协议的UDP和TCP
  3. Eclipse如何不使用alt+/来实现自动提示
  4. elasticsearch映射相关字段定义,属性定义,及动态映射(marvel插件方式)mapping
  5. 在html中写三角,css3怎么写三角形?
  6. 2021年怎么自学前端?
  7. 关于结构体数据的读写
  8. webstorm怎么建php文件夹,webstorm怎么创建css
  9. 理想制动力分配曲线matlab源代码_宝马进入“血拼”状态,动力倍儿棒
  10. 杨云 中科院计算机所,专家人才库数据----中国科学院计算技术研究所