如果我使用 Mockito,我什至需要 Guice 吗? [英] If I use Mockito do I even need Guice?

查看:19
本文介绍了如果我使用 Mockito,我什至需要 Guice 吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在学习依赖注入(例如 Guice),在我看来,主要驱动因素之一,可测试性,已经被 Mocking(例如 Mockito)很好地涵盖了.依赖注入和Mocking框架的区别(Ninject vs RhinoMock orMoq) 很好地总结了 Dependency Injection 和 Mockito 之间的共性,但它没有提供关于在功能重叠时使用哪种方法的指导.

I've been learning about Dependency Injection (e.g. Guice) and it seems to me one of the main drivers, testability, is covered already quite nicely by Mocking (e.g. Mockito). Difference between Dependency Injection and Mocking framework (Ninject vs RhinoMock or Moq) is a nice summary of commonality between Dependency Injection and Mockito but it doesn't offer guidance on which to use when they overlap in capability.

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

I'm about to design an API and I'm wondering if I should:

A] 仅使用 Mockito

A] Use Mockito only

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

B] Use Guice and design two implementation of interfaces--one for real and one for testing

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

C] Use Mockito AND Guice together--if so, how?

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

I'm guessing the right answer is C, to use them both, but I'd like some words of wisdom: where can I use either Dependency Injection or Mocking, which should I choose and why?

推荐答案

Guice 和 Mockito 有非常不同和互补的角色,我认为他们最好一起工作.

Guice and Mockito have very different and complementary roles, and I'd argue that they work best together.

考虑这个人为的示例类:

Consider this contrived example class:

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;
  }
}

注意这门课做了多少额外的工作:你实际上并没有使用你的轮胎或轮子,只是创建一个工作的引擎,而且没有办法替代你的轮胎或轮子:任何汽车,无论是生产中还是测试中,必须有真正的轮胎、真正的轮子、真正的引擎和真正记录到服务器的真正记录器.你先写哪一部分?

Notice how much extra work this class does: You don't actually use your Tires or Wheels other than to create a working Engine, and there's no way to substitute your Tires or Wheels: Any car, in production or in test, must have real Tires, real Wheels, a real Engine, and a real Logger that really logs to a server. Which part do you write first?

让我们让这个类对 DI 友好:

Let's make this class DI-friendly:

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 的强大之处在于以松散耦合的方式构建大型依赖图.)

Now the CarController doesn't have to concern itself with the tires, wheels, engine, or log output, and you can substitute in whichever Engine and Logger you'd like by passing them into the constructor. In this way, DI is useful in production: With the change of a single module, you can switch your Logger to log to a circular buffer or local file, or switch to a supercharged Engine, or upgrade to SnowTires or RacingTires separately. This also makes the class more testable, because now substituting out implementations becomes much easier: you can write your own test doubles such as FakeEngine and DummyLogger and put them in your CarControllerTest. (Of course, you can also create setter methods or alternate constructors, and you can design the class in this way without actually using Guice. Guice's power comes from constructing large dependency graphs in a loosely-coupled way.)

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

Now, for those test doubles: In a world with only Guice but no Mockito, you would have to write your own Logger-compatible test double and your own Engine-compatible test double:

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,这变得自动化,具有更好的堆栈跟踪和更多功能:

With Mockito, that becomes automatic, with better stack traces and many more features:

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

...这就是为什么他们合作得这么好.依赖注入使您有机会编写组件(CarController),而无需考虑其依赖项的依赖项(Tires、Wheels、ServerLogOutput),并可以随意更改依赖项实现.然后,Mockito 让您可以使用最少的样板创建这些替换实现,这些样板可以随心所欲地注入到任何地方.

...and that's why they work so well together. Dependency injection gives you the chance to write a component (CarController) without concerning yourself with its dependencies' dependencies (Tires, Wheels, ServerLogOutput), and to change dependency implementations at your will. Then Mockito lets you create these replacement implementations with minimal boilerplate, which can be injected wherever and however you'd like.

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

Side note: Neither Guice nor Mockito should be a part of your API, as you mention in the question. Guice can be a part of your implementation details, and may be a part of your constructor strategy; Mockito is a part of your testing and shouldn't have any effect on your public interface. Nevertheless, the choice of frameworks for OO design and testing is an excellent discussion to have before starting your implementation.

更新,合并评论:

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

  • Typically, you won't actually use Guice in unit tests; you'll call the @Inject constructors manually with the assortment of objects and test doubles you prefer. Remember that it's easier and cleaner to test state rather than interactions, so you'll never want to mock data objects, you'll almost always want to mock remote or asynchronous services, and that expensive and stateful objects might be better represented with lightweight fakes. Don't be tempted to over-use Mockito as the only solution.

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

Mockito has its own "dependency injection" feature called @InjectMocks, which will replace the system-under-test's fields with @Mock fields of the same name/type even if there are no setters. This is a nice trick to replace dependencies with mocks, but as you noted and linked, it will fail silently if dependencies are added. Given that downside, and given that it misses out on much of the design flexibility that DI provides, I've never had a need to use it.

这篇关于如果我使用 Mockito,我什至需要 Guice 吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆