Unit testing 如果我用Mockito我需要Guice吗?

Unit testing 如果我用Mockito我需要Guice吗?,unit-testing,dependency-injection,mocking,mockito,guice,Unit Testing,Dependency Injection,Mocking,Mockito,Guice,我一直在学习依赖注入(例如Guice),在我看来,模拟(例如Mockito)已经很好地涵盖了主要驱动因素之一的可测试性。这是对依赖注入和Mockito之间的共性的一个很好的总结,但它没有提供当它们在功能上重叠时应该使用的指导 我将要设计一个API,我想知道我是否应该: A] 仅使用Mockito B] 使用Guice并设计两个接口实现——一个用于real,另一个用于测试 C] 一起使用Mockito和Guice——如果是,如何使用 我猜正确的答案是C,两者都用,但我想说几句智慧的话:我在哪里可以

我一直在学习依赖注入(例如Guice),在我看来,模拟(例如Mockito)已经很好地涵盖了主要驱动因素之一的可测试性。这是对依赖注入和Mockito之间的共性的一个很好的总结,但它没有提供当它们在功能上重叠时应该使用的指导

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

A] 仅使用Mockito


B] 使用Guice并设计两个接口实现——一个用于real,另一个用于测试

C] 一起使用Mockito和Guice——如果是,如何使用


我猜正确的答案是C,两者都用,但我想说几句智慧的话:我在哪里可以使用依赖注入或Mocking,我应该选择哪一种,为什么?

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


Guice和Mockito扮演着非常不同和互补的角色,我认为他们合作得最好

考虑这个人为设计的示例类:

公共类CarController{
专用最终轮胎=新轮胎();
专用最终车轮=新车轮(轮胎);
私人最终发动机=新发动机(车轮);
私人记录器引擎记录器;
公共记录器启动(){
engineLogger=新engineLogger(引擎,新服务器Logoutput());
发动机起动();
engineLogger.recordEvent(发动机已启动);
返回引擎记录器;
}
}
请注意这个类做了多少额外的工作:除了创建一个工作的引擎之外,您实际上并没有使用您的轮胎或车轮,并且没有办法替换您的轮胎或车轮:任何正在生产或测试的汽车都必须有真正的轮胎、真正的车轮、真正的发动机和真正登录到服务器的真正的记录器。你先写哪部分

让我们让这个类变得更友好:

public类CarController{/*带注入*/
私人最终引擎;
私人最终提供商loggerProvider;
私人记录器引擎记录器;
/**使用Guice,您通常可以将构造函数包保持私有*/
@注入公共汽车(发动机,供应商loggerProvider){
这个。发动机=发动机;
this.loggerProvider=loggerProvider
}
公共记录器启动(){
engineLogger=loggerProvider.get();
发动机起动();
engineLogger.recordEvent(发动机已启动);
返回引擎记录器;
}
}
现在,CarController不必关心轮胎、车轮、引擎或日志输出,您可以通过将它们传递给构造函数来替换任何引擎和日志。这样,DI在生产中很有用:通过更改单个模块,您可以将记录器切换到循环缓冲区或本地文件,或切换到增压引擎,或分别升级到雪地轮胎或RacingTires。 这也使得类更易于测试,因为现在替换实现变得更容易:您可以编写自己的实现,如FakeEngine和DummyLogger,并将它们放入您的CarControllerTest中。(当然,您也可以创建setter方法或替代构造函数,并且可以用这种方式设计类,而不必实际使用Guice。Guice的能力来自于以松散耦合的方式构造大型依赖关系图。)

现在,对于这些双重测试:在一个只有Guice而没有Mockito的世界中,您必须编写自己的记录器兼容双重测试和引擎兼容双重测试:

公共类FakeEngine实现引擎{
RuntimeExceptionTorOW=null;
int callsToStart=0;
Logger returnLogger=null;
@覆盖公共记录器启动(){
如果(ExceptionTorOW!=null)抛出ExceptionTorOW;
callsToStart+=1;
返回记录器;
}
}
使用Mockito,这将变得自动化,具有更好的堆栈跟踪和更多功能:

@Mock引擎Mock引擎;
//要验证:
验证(模拟引擎).start();
//或存根:
doThrow(新运行时异常()).when(mockEngine.start();
…这就是他们合作得这么好的原因。依赖项注入使您有机会编写组件(CarController),而不必考虑其依赖项的依赖项(轮胎、轮子、服务器登录输出),并可以随意更改依赖项实现。然后,Mockito允许您使用最少的样板创建这些替换实现,可以在任何地方以任何方式注入样板

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


更新,包括评论:

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

  • Mockito有自己的“依赖注入”功能,称为
    @InjectMocks
    ,它将用相同名称/类型的
    @Mock
    字段替换被测系统的字段,即使没有设置器。这是一个很好的技巧,可以用mock替换依赖项,但正如您所注意到并链接的,如果添加了依赖项。考虑到这一缺点,并考虑到它错过了DI提供的许多设计灵活性,我从来没有必要使用它


您可以在单元测试和依赖项注入中同时使用这两种方法