【问题标题】:If I use Mockito do I even need Guice?如果我使用 Mockito,我什至需要 Guice 吗?
【发布时间】:2014-12-02 23:18:34
【问题描述】:

我一直在学习依赖注入(例如 Guice),在我看来,主要驱动因素之一,可测试性,已经被 Mocking(例如 Mockito)很好地涵盖了。 Difference between Dependency Injection and Mocking framework (Ninject vs RhinoMock or Moq) 很好地总结了 Dependency Injection 和 Mockito 之间的共性,但它没有提供在功能重叠时使用的指南。

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

A] 仅使用 Mockito

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

C] 一起使用 Mockito 和 Guice - 如果可以,如何使用?

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

【问题讨论】:

  • 您可以在单元测试中同时使用两者,在生产代码中单独使用依赖注入。在单元测试中,可以使用依赖注入来注入 mocks。
  • 谢谢@AndyThomas,注入模拟有什么好处?在我的班级上调用mock() 然后调用stub() 来覆盖行为似乎更少的代码。使用依赖注入,我是否不必编写Module 的实现,然后编写一个实现我要测试的类的整个接口的类?似乎有很多代码。我敢肯定没有“正确答案”,但我会欣赏智慧之言。
  • 你也可以这样做,使用非最终类和方法。有时,依赖注入的设计除了单元测试之外还有其他好处,在这种情况下,您可以在单元测试期间利用它。
  • “使用 Guice 并设计两种接口实现——一种用于真实,一种用于测试”。这里的一个问题是您可能需要数十个(单元测试)实现来测试您的代码。虽然简单的单元测试实现可能只是在你的 void 方法中“什么都不做”......或者在 getmethods 中返回一个默认对象......(等等)............你可能需要单元测试实现来做不同的事情。就像抛出异常......或者从 get 方法返回一个空项......这就是为什么你需要一个模拟框架......它使编码这几十个单元测试情况...... ..更容易。

标签: unit-testing dependency-injection mocking mockito guice


【解决方案1】:

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。 这也使该类更易于测试, 因为现在替换实现变得更加容易:您可以编写自己的test doubles,例如 FakeEngine 和 DummyLogger,并将它们放入您的 CarControllerTest。 (当然,您也可以创建 setter 方法或替代构造函数,并且您可以在不实际使用 Guice 的情况下以这种方式设计类。Guice 的强大之处在于以松散耦合的方式构建大型依赖图。)

现在,对于那些测试替身:在一个只有 Guice 而没有 Mockito 的世界中,您必须编写自己的与 Logger 兼容的测试替身和自己的与引擎兼容的测试替身:

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),而无需考虑其依赖项的依赖项(Tires、Wheels、ServerLogOutput),并可以随意更改依赖项实现。然后,Mockito 让您可以使用最少的样板来创建这些替换实现,这些样板可以随心所欲地注入到任何地方。

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


更新,合并 cmets:

  • 通常情况下,您实际上不会在单元测试中使用 Guice。您将使用各种对象和您喜欢的test doubles 手动调用@Inject 构造函数。请记住,测试状态比测试交互更容易和更清晰,因此您永远不会想要模拟数据对象,您几乎总是想要模拟远程或异步服务,并且昂贵和有状态的对象可能更好地表示为轻量级假货.不要试图过度使用 Mockito 作为唯一的解决方案。

  • Mockito 有自己的“依赖注入”功能,称为@InjectMocks,即使没有设置器,它也会用相同名称/类型的@Mock 字段替换被测系统的字段。这是一个用模拟替换依赖项的好技巧,但正如您指出和链接的那样,it will fail silently 如果添加了依赖项。考虑到这个缺点,并且考虑到它错过了 DI 提供的大部分设计灵活性,我从来没有需要使用它。

【讨论】:

  • 说得好......我想写但没有文字。 :-)
  • 感谢@JeffBowman,这是一个非常详细的答案。感谢您对 Guice 和 Mockito 都没有作为我的 API 的一部分公开的评论。那我就把它们藏起来。
  • 再次感谢@JeffBowman,我被依赖注入对干净设计的好处所吸引。但是对于测试,我仍然感到困惑。 When given a choice to call Mockito's mock() and stub() or use Guice to inject Test Doubles, which way would you go and why?
  • 如果问题是是否使用 Mockito 与其他测试替身,这是一个取决于模拟对象代表多少状态的判断调用:你不应该模拟数据对象,总是模拟远程服务,并考虑昂贵的有状态对象的伪造品。阅读the linked article 以获得更多指导。 Guice 本身可能在您的单元测试中没有位置,但是在为 DI 设计组件之后,您可以使用您想要的任何测试替身(或实际实现)调用那些有用的 @Inject 构造函数。
  • 一篇好文章 Mockito: Why You Should Not Use InjectMocks Annotation to Autowire Fields 解释了 Mockito 的依赖注入标签 @InjectMocks 如果您向被测类添加更多依赖项,可能会静默失败。建议使用 Spring 进行显式依赖注入(我假设 Guice 是 Spring 的合适替代品)。
【解决方案2】:

看看 Jukito,它是 Mockito、Guice 和 Junit 的混合体。

https://github.com/ArcBees/Jukito

http://jukito.arcbees.com/

【讨论】:

  • 哇@echen 太强大了。感谢分享。
猜你喜欢
  • 1970-01-01
  • 2020-02-16
  • 2023-03-08
  • 2016-01-10
  • 2022-08-04
  • 2015-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多