如果使用Mockito,我是否还需要Guice?

迈克尔·奥索夫斯基

我一直在学习依赖注入(例如Guice),在我看来,主要的驱动因素之一(可测试性)已经被Mocking(例如Mockito)很好地涵盖了。依赖注入和模拟框架之间的差异(Ninject与RhinoMock或Moq)很好地总结了依赖注入和Mockito之间的共性,但是当功能重叠时,它不提供使用方法的指导。

我将要设计一个API,我想知道是否应该:

A]仅使用Mockito

B]使用Guice并设计两种接口实现-一种用于实际,一种用于测试

C]一起使用Mockito和Guice-如果是这样,怎么做?

我猜正确的答案是C,要同时使用它们,但我想说一句智慧:我在哪里可以使用依赖注入或模拟,应该选择哪个,为什么?

杰夫·鲍曼

Guice和Mockito扮演着非常不同和互补的角色,我认为他们可以最好地合作。

考虑一下这个人为的示例类:

public class CarController {
  private final Tires tires = new Tires();
  private final Wheels wheels = new Wheels(tires);
  private final Engine engine = new Engine(wheels);
  private Logger engineLogger;

  public Logger start() {
    engineLogger = new EngineLogger(engine, new ServerLogOutput());
    engine.start();
    engineLogger.recordEvent(ENGINE_STARTED);
    return engineLogger;
  }
}

请注意,该类需要做些额外的工作:除了创建可用的引擎之外,您实际上没有使用轮胎或车轮,也没有替代轮胎或车轮的方法:任何在生产或测试中的汽车都必须拥有真实的轮胎。轮胎,真实的车轮,真实的引擎以及真实记录到服务器的真实记录仪。您首先写哪一部分?

让我们使此类DI友好:

public class CarController { /* with injection */
  private final Engine engine;
  private final Provider<Logger> loggerProvider;
  private Logger engineLogger;

  /** With Guice, you can often keep the constructor package-private. */
  @Inject public Car(Engine engine, Provider<Logger> loggerProvider) {
    this.engine = engine;
    this.loggerProvider = loggerProvider
  }

  public Logger start() {
    engineLogger = loggerProvider.get();
    engine.start();
    engineLogger.recordEvent(ENGINE_STARTED);
    return engineLogger;
  }
}

现在,CarController不必关心轮胎,车轮,发动机或原木输出,您可以通过将它们传递给构造函数来替换所需的Engine和Logger。这样,DI在生产中非常有用:只需更改单个模块,即可将Logger切换为记录到循环缓冲区或本地文件,或切换为增压引擎,或分别升级为SnowTires或RacingTires。这也使类更具可测试性,因为现在替换实现变得更加容易:您可以编写自己的测试双打例如FakeEngine和DummyLogger,并将它们放入CarControllerTest中。(当然,您还可以创建setter方法或备用构造函数,并且可以在不实际使用Guice的情况下以这种方式设计类。Guice的强大功能来自以松散耦合的方式构造大型依赖图。)

现在,对于那些双重测试:在只有Guice而没有Mockito的世界中,您将不得不编写自己的Logger兼容测试双重和自己的Engine兼容测试双重:

public class FakeEngine implements Engine {
  RuntimeException exceptionToThrow = null;
  int callsToStart = 0;
  Logger returnLogger = null;

  @Override public Logger start() {
    if (exceptionToThrow != null) throw exceptionToThrow;
    callsToStart += 1;
    return returnLogger;
  }
}

使用Mockito,它将自动进行,具有更好的堆栈跟踪和更多功能:

@Mock Engine mockEngine;
// To verify:
verify(mockEngine).start();
// Or stub:
doThrow(new RuntimeException()).when(mockEngine).start();

...这就是为什么他们在一起表现如此出色。依赖注入为您提供了编写组件(CarController)的机会,而无需考虑其依赖项的依赖项(轮胎,车轮,ServerLogOutput),并可以随意更改依赖项的实现。然后,Mockito使您可以使用最少的样板创建这些替换实现,可以将样板插入任何位置,无论您愿意如何。

旁注:正如您在问题中提到的,Guice和Mockito都不应该是您的API的一部分Guice可以是实现细节的一部分,也可以是构造器策略的一部分; Mockito是测试的一部分,对您的公共界面不应该有任何影响。但是,在开始实施之前,对面向对象设计和测试框架的选择是一个很好的讨论。


更新,并包含评论:

  • 通常,您不会在单元测试中实际使用Guice。您将使用各种对象手动调用@Inject构造函数,并测试您喜欢的双打请记住,测试状态比交互更容易,更干净,因此您永远不需要模拟数据对象,几乎总是想模拟远程或异步服务,并且昂贵的有状态对象最好用轻量级的伪造品来表示。 。不要试图过度使用Mockito作为唯一解决方案。

  • Mockito有自己的“依赖注入”功能,称为@InjectMocks@Mock即使没有设置器,它也将用相同名称/类型的字段替换被测系统的字段这是一个用模拟代替依赖关系的好技巧,但是正如您指出并链接的那样,如果添加了依赖关系它将无提示地失败考虑到它的缺点,并且由于它错过了DI提供的许多设计灵活性,所以我从不需要使用它。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

如果我已经在使用Modernizr,那么我是否还需要HTML5 Shiv?

如果我正在使用ES6模块,是否还需要模块加载器?

srcset,如果我已指定每个图像的宽度,我是否还需要写“ 1x,2x,3x”?

如果您使用的是Service Workers,您是否还需要缓存控制标头?

如果我们可以使用Selenium,为什么还需要像BeautifulSoup这样的解析器?

如果仅更改数据批注的Display属性,是否还需要运行Add-Migration?

如果我们使用notify_one()唤醒线程,在C ++中是否还需要yield()?

如果我以前使用过memset,是否还需要将ptr设置为NULL?

如果已经从fs节点使用AccessSync()方法检查了文件,是否还需要检查ExistSync()?

我们是否还需要为按字段排序的集合设置索引?

如果NSView使用自动布局,它的所有子视图是否还需要使用自动布局进行定位?

如果我自动加载模块,是否还需要包含它

如果我有防病毒软件,还需要更新Windows吗?

C ++完美转发:如果可以使用const_cast(),为什么还需要forward()?

如果我在Cassandra上启用了自动压缩,是否还需要使用`nodetool compact`?

我是否还需要交换分区?

除CPU风扇外,我的系统中是否还需要其他风扇?

如果“使用” BinaryWriter,是否还需要调用Close?- F#

如果我使用Heroku调度程序,是否还需要延迟工作?

我如何知道平台项目是否还需要NuGet软件包

如果我已经有了 Kubernetes(或 mesos),为什么还需要使用 Spring Cloud?

我是否还需要使用 compareTo 方法覆盖 hashCode 和 equals 方法?

我还需要使用虚拟内存吗?

登录 Azure - 我是否还需要 NLog(或等效的)

如果我们有 <!DOCTYPE html>,为什么还需要 <html> 标签?

如果我们可以更改线程池使用的线程数,为什么还需要节点集群?

如果我在几秒钟后卸载 React 组件,是否还需要 Useeffect 中的依赖数组?

如果我的最低部署目标是 iOS 13.0,我的班级中是否还需要 @availible 13.0 属性?

我还需要安装 CocoaPod 吗?