2014-12-02 5 views
4

Я узнал об Injection Dependency (например, Guice), и мне кажется, что один из основных драйверов, тестируемость, уже довольно хорошо покрыт Mocking (например, Mockito). Difference between Dependency Injection and Mocking framework (Ninject vs RhinoMock or Moq) - это хорошее резюме общности между Injection Dependency и Mockito, но оно не дает рекомендаций по использованию, когда они перекрываются в возможностях.Если я использую Mockito, мне даже нужен Guice?

Я собираюсь разработать API, и мне интересно, если я должен:

A] Использование Mockito только

B] Использование Guice и дизайн два реализация интерфейсов - один для реального и один для тестирования

C] Использование Mockito AND Guice вместе - если да, то как?

Я предполагаю, что правильный ответ - это C, чтобы использовать их оба, но мне бы хотелось несколько слов мудрости: где я могу использовать либо инъекцию зависимостей, либо насмешку, которую я должен выбрать и почему?

+1

Вы можете использовать их как в единичных тестах, так и в зависимостях только в производственном коде. В модульных тестах вы можете использовать инъекцию зависимостей для инъекций mocks. –

+0

Спасибо @ AndyThomas, в чем преимущество инъекций издевательств? Кажется, что меньше кода просто вызывает 'mock()' в моем классе, а затем вызывает 'stub()' для переопределения поведения. С внедрением зависимостей мне не придется писать реализацию 'Module', а затем писать класс, который реализует весь интерфейс класса, который я хочу проверить? Похоже, много кода. Я уверен, что нет «правильного ответа», но я был бы признателен за мудрость. –

+1

Вы также можете сделать это с помощью не заключительных классов и методов. Иногда проектирование для инъекций зависимостей имеет преимущества помимо модульного тестирования, в каких случаях вы можете использовать его во время модульного тестирования. –

ответ

14

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, мне никогда не приходилось его использовать.

+1

Хорошо сказал ... что я хотел написать, но не имел слов. :-) – The111

+1

Спасибо @JeffBowman, это супер подробный ответ.Спасибо за ваш комментарий о том, что ни Guice, ни Mockito не были представлены как часть моего API. Тогда я их скрою. –

+0

Еще раз спасибо @JeffBowman, я продаюсь за преимущества Injection Dependency для чистого дизайна. Но для тестирования я все еще смущен. Когда вам предлагается выбрать Mockito's mock() и stub() или использовать Guice для инъекции двойников, каким образом вы пойдете и почему? –

Смежные вопросы