我一直在学习依赖注入(例如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] 删除。
我来说两句