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 не должны касаться себя с шины, колеса, двигатель, или войти выход, и вы можете заменить в какой бы двигатель и Logger вам нужно передать их в конструктор. Таким образом, DI полезен в производстве: с изменением одного модуля вы можете переключить свой Logger на журнал на круговой буфер или локальный файл или переключиться на двигатель с наддувом или перейти на SnowTires или RacingTires отдельно. Это также делает класс более тестируемым,, потому что теперь замену реализации становится намного проще: вы можете написать свой собственный test doubles, такой как FakeEngine и DummyLogger, и поместить их в свой тест CarControllerTest. (Разумеется, вы также можете создавать методы настройки или альтернативные конструкторы, и вы можете создать класс таким образом, фактически не используя 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 - отличная дискуссия, которая должна быть выполнена до начала вашей реализации.
Update, включающие комментарии:
Как правило, вы не будете фактически использовать Guice в модульных тестов; вы будете называть конструкторы @Inject вручную с помощью набора объектов и test doubles, которые вы предпочитаете. Помните, что легче и проще тестировать состояние, а не взаимодействовать, поэтому вы никогда не захотите имитировать объекты данных, вы почти всегда хотите издеваться над удаленными или асинхронными службами и что дорогие и объекты с сохранением состояния могут быть лучше представлены легкими подделки , Не будьте соблазнены чрезмерным использованием Mockito в качестве единственного решения.
Mockito имеет свою собственную «инъекцию зависимостей» функцию под названием @InjectMocks
, который заменит поля системной испытуемых с @Mock
полеем одного и тем же именем/типом, даже если нет сеттеров. Это хороший трюк для замены зависимостей с помощью mocks, но, как вы отметили и связали, it will fail silently, если зависимости добавлены. Учитывая этот недостаток, и учитывая, что он пропускает большую часть гибкости дизайна, которую предоставляет DI, мне никогда не приходилось его использовать.
Вы можете использовать их как в единичных тестах, так и в зависимостях только в производственном коде. В модульных тестах вы можете использовать инъекцию зависимостей для инъекций mocks. –
Спасибо @ AndyThomas, в чем преимущество инъекций издевательств? Кажется, что меньше кода просто вызывает 'mock()' в моем классе, а затем вызывает 'stub()' для переопределения поведения. С внедрением зависимостей мне не придется писать реализацию 'Module', а затем писать класс, который реализует весь интерфейс класса, который я хочу проверить? Похоже, много кода. Я уверен, что нет «правильного ответа», но я был бы признателен за мудрость. –
Вы также можете сделать это с помощью не заключительных классов и методов. Иногда проектирование для инъекций зависимостей имеет преимущества помимо модульного тестирования, в каких случаях вы можете использовать его во время модульного тестирования. –