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

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

问题描述

我一直在学习依赖注入(例如Guice),而在我看来,Mocking(例如Mockito)已经很好地介绍了可执行性的主要驱动因素之一。 依赖注入与模拟框架之间的差异(Ninject vs RhinoMock或Moq)是Dependency Injection和Mockito之间的共性的一个很好的总结,但是当它们的能力重叠时,它不提供使用哪些指南。



I我要设计API,我想知道是否应该:



A]仅使用Mockito



B]使用Guice并设计两个接口的实现 - 一个是真实的,一个用于测试



C]将Mockito和Guice一起使用 - 如果是,如何? p>

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

解决方案

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



考虑这个有创意的示例类:

  public class CarController {
private final轮胎轮胎=新轮胎();
私人决赛车轮=新车轮(轮胎);
私人最终引擎引擎=新引擎(轮子);
private Logger engineLogger;

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

注意这个课程额外的工作量:你实际上,除了创造一个工作引擎之外,实际上使用你的轮胎或轮子,没有办法替代您的轮胎或车轮:任何生产或测试中的汽车都必须有真正的轮胎,真实的车轮,真正的发动机和真正的Logger真正登录到服务器。你首先写哪一部分?



让我们这个课堂DI-friendly:

私有的最终引擎引擎; 
私人最终提供者< Logger> loggerProvider;
private Logger engineLogger;

/ **使用Guice,您通常可以保持构造函数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兼容测试双和你自己的引擎兼容的测试double:

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

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

使用Mockito,这将自动化,具有更好的堆栈跟踪还有更多功能:

  @Mock Engine mockEngine; 
//验证:
verify(mockEngine).start();
//或stub:
doThrow(new RuntimeException())。when(mockEngine).start();

...这就是为什么他们一起工作的原因。依赖注入使您有机会编写一个组件(CarController),而不用依赖依赖关系(Tires,Wheels,ServerLogOutput),并根据自己的意愿更改依赖性实现。那么Mockito可以让你用最少的样板创建这些替代的实现,可以随时随地注入。



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






更新,包含注释:




  • 通常,您不会在单元测试中实际使用Guice;您将通过对象的分类手动调用@Inject构造函数,而您更喜欢测试双打。记住,测试状态而不是交互更容易和更干净,所以你永远不会想要模拟数据对象,你几乎总是想要模拟远程或异步服务,并且昂贵和有状态的对象可能会被更好的代表轻量级的假货。不要试图过度使用Mockito作为唯一的解决方案。


  • Mockito有自己的依赖注入功能,名为 @ InjectMocks ,即使没有设置器,它将用相同名称/类型的 @Mock 字段替换系统测试下的字段。这是一个很好的技巧,用嘲讽替代依赖关系,但正如你所指出的和链接的,它将静默地失败,如果依赖关系被添加。鉴于这种缺点,鉴于DI错过了DI提供的大部分设计灵活性,我从来没有必要使用它。



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.

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

A] Use Mockito only

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

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

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

解决方案

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?

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

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.)

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

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();

...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.

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.


Update, incorporating comments:

  • 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 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天全站免登陆