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 提供的大部分设计灵活性,我从来没有需要使用它。