2011-12-27 3 views
5

Я читал (и экспериментировал) несколько Java mocking API, таких как Mockito, EasyMock, JMock и PowerMock. Мне нравится каждый из них по разным причинам, но в конечном счете решил Мокито. Обратите внимание:, однако, что это не вопрос вопрос о том, какую структуру использовать - вопрос действительно относится к любой mocking framework, хотя решение будет выглядеть по-другому, поскольку API (очевидно) разные.Величина проверки поведения

Как и во многих других вещах, вы читаете руководства, вы следуете примерам, и вы перебираете несколько образцов кода в проекте песочницы. Но тогда, когда наступает время, чтобы на самом деле использовать эту вещь, вы начинаете задыхаться - и вот где я.

I действительно, действительно нравится идея насмешки. И да, я знаю жалобы на насмешки, ведущие к «хрупким» тестам, которые слишком сильно связаны с тестируемыми классами. Но пока я сам не дойду до такого осознания, я действительно хочу дать насмешку, чтобы увидеть, может ли он добавить хорошую ценность для моих модульных тестов.

Теперь я пытаюсь активно использовать макеты в своих модульных тестах. Mockito позволяет как стучать, так и насмехаться. Предположим, у нас есть объект Car, который имеет метод getMaxSpeed(). В Mockito, мы могли бы окурок его следующим образом:

Car mockCar = mock(Car.class); 
when(mockCar.getMaxSpeed()).thenReturn(100.0); 

Этих «заглушки» на Car объект всегда возвращать 100.0 как максимальная скорость нашей машины.

Моя проблема заключается в том, что после написания нескольких модульных тестов уже ... все, что я делаю, - это укусить моих соавторов! Я не использую ни одного макетного метода (verify и т. Д.), Доступных мне!

Я понимаю, что я застрял в состоянии «stubbing» ума », и я не могу сломаться. Все это чтение, и все это возбуждение, основываясь на использовании mocks в моем модульном тестировании и ... Я не могу придумать ни одного варианта использования для проверки поведения.

Итак, я сделал резервную копию и перечитал Fowler's article и другие литературные стили BDD, и все же я просто «не получаю» ценность проверки поведения для двойных соавторов тестов.

I знаю что я чего-то не хватает, я просто не уверен в чем. Может ли кто-нибудь дать мне конкретный пример (или даже набор примеров!), Используя, скажем, этот класс Car, и продемонстрировать, когда модульный тест, проверяющий поведение, благоприятствует проверке состояния?

Заранее благодарим за любые подталкивания в правильном направлении!

+0

Не могли бы вы показать нам один из ваших занятий? Например, тот, который является клиентом автомобиля, который был заострен выше. Тогда, возможно, мы могли бы предложить, как протестировать его с помощью mocks. –

+0

Привет, Том. Мне нравится пример JB Nizet лучше, чем автомобиль. Этот пример, наряду с объяснением JB, действительно подчеркивает источник моей путаницы. Мой комментарий под ответом JB объясняет это. – IAmYourFaja

ответ

2

Ну, если тестируемый объект вызывает соавтор с вычисленным значением, и тест должен проверить, что вычисление верное, тогда правильная работа над выполнением макетчика-коррелятора. Пример:

private ResultDisplayer resultDisplayer; 

public void add(int a, int b) { 
    int sum = a + b; // trivial example, but the computation might be more complex 
    displayer.display(sum); 
} 

Очевидно, что в этом случае вы должны будете дразнить Displayer, и убедитесь, что его метод отображения был назван, со значением 5, если 2 и 3 являются аргументами метода add.

Если все, что вы делаете со своими сотрудниками, это вызовы getters без аргументов или с аргументами, которые являются прямыми входами тестируемого метода, то, возможно, завершение должно быть достаточным, если код не может получить значение от двух разных соавторов, и вы хотите убедитесь, что вызван соответствующий соавтор.

Пример:

private Computer noTaxComputer; 
private Computer taxComputer; 

public BigDecimal computePrice(Client c, ShoppingCart cart) { 
    if (client.isSubjectToTaxes()) { 
     return taxComputer.compute(cart); 
    } 
    else { 
     return noTaxComputer.compute(cart); 
    } 
} 
+0

Спасибо JB! Я думаю, что мы сейчас находимся в корне моей путаницы. Во втором абзаце вы указываете: «... вам придется издеваться над дисплеем и убедиться, что его метод отображения был вызван со значением 5 ...» Я понимаю, почему нам нужно проверить, отображается ли дисплей значение 5 ... я не понимаю, почему мы должны проверить, что 'displayer.display' был даже вызван! Почему мы не можем просто написать JUnit, чтобы утверждать, что 'assertEquals (displayer.getDisplay(), 5)'? Я просто не понимаю, почему проверка того, что выполняемый метод 'display (int)' добавляет значение к модульному тесту. Любые идеи/контрагенты? Спасибо! – IAmYourFaja

+0

Поскольку способ отображения дисплея может сделать что-то гораздо более сложное, чем просто сохранить значение в поле, доступном через геттер. Он может отправить его в поток, на ЖК-дисплей, в какую-то базу данных и т. Д. Даже если он сохранит его в поле, создание реального монитора может быть затруднено в тестовой среде, поскольку оно зависит от базы данных, JMS-движка или другой сложной инфраструктуры. –

+0

Дисплей может также вызываться несколько раз во время метода: один раз с 2, один раз с 3 и один раз с 5. Если вы хотите проверить каждое промежуточное состояние, у вас нет другого выбора, кроме как издеваться над дисплеем. –

2

Мне нравится ответ @JB Nizet, но вот еще один пример. Предположим, вы хотите сохранить автомобиль в базе данных с помощью Hibernate после внесения некоторых изменений. Таким образом, у вас есть класс, как это:

public class CarController { 

    private HibernateTemplate hibernateTemplate; 

    public void setHibernateTemplate(HibernateTemplate hibernateTemplate) { 
    this.hibernateTemplate = hibernateTemplate; 
    } 

    public void accelerate(Car car, double mph) { 
    car.setCurrentSpeed(car.getCurrentSpeed() + mph); 
    hibernateTemplate.update(car); 
    } 
} 

Для проверки метода ускорения, вы могли бы просто использовать заглушки, но вы бы не тест конкурировать.

public class CarControllerTest { 
    @Mock 
    private HibernateTemplate mockHibernateTemplate; 
    @InjectMocks 
    private CarController controllerUT; 

    @Test 
    public void testAccelerate() { 
    Car car = new Car(); 
    car.setCurrentSpeed(10.0); 
    controllerUT.accelerate(car, 2.5); 
    assertThat(car.getCurrentSpeed(), is(12.5)); 
    } 
} 

Этот тест проходит, и делает проверить вычисления, но мы не знаем, если новая скорость автомобиля была сохранена или нет. Чтобы сделать это, нам нужно добавить:

verify(hibernateTemplate).update(car); 

Теперь предположим, что если вы пытаетесь ускорить прошлое скорость макс, вы ожидаете ускорение и обновление не произойдет. В этом случае, вы хотите:

@Test 
public void testAcceleratePastMaxSpeed() { 
    Car car = new Car(); 
    car.setMaxSpeed(20.0); 
    car.setCurrentSpeed(10.0); 
    controllerUT.accelerate(car, 12.5); 
    assertThat(car.getCurrentSpeed(), is(10.0)); 
    verify(mockHibernateTemplate, never()).update(car); 
} 

Этот тест не будет проходить с нашей текущей реализации CarController, но оно не должно. Он показывает, что вам нужно сделать больше работы для поддержки этого случая, и что одним из требований является то, что вы не пытаетесь писать в базу данных в этом случае.

В принципе, убедитесь, что вы должны использовать именно то, на что это похоже - проверить, что что-то произошло (или не произошло). Если факт, что это произошло или нет, это не то, что вы пытаетесь проверить, а затем пропустите. Возьмем второй пример, который я сделал. Можно утверждать, что, поскольку значение не было изменено, на самом деле не имеет значения, было ли вызвано обновление или нет. В этом случае вы можете пропустить шаг проверки во втором примере, так как реализация accelerate была бы правильной в любом случае.

Я надеюсь, что это не похоже, что я делаю случай для проверки. Это может сделать ваши тесты очень хрупкими. Но он также может «проверять», что важные вещи, которые должны были произойти, произошли.

0

Мой взгляд на это, что каждый тест должен содержать ЯВНО

  • заглушек, а также один или несколько assert s ИЛИ
  • один или несколько verify s.

но не оба.

Мне кажется, что в большинстве тестовых классов вы получаете смесь тестовых примеров «заглушить и утверждать» и «проверять» тестовые примеры. Является ли тестовый пример «заглушкой и утверждением» или «проверка» зависит от того, важно ли значение, возвращаемое соавтором для теста. Мне нужно два примера, чтобы проиллюстрировать это.

Предположим, у меня есть класс Investment, который имеет значение в долларах. Его конструктор устанавливает начальное значение. Он имеет метод addGold, который увеличивает значение Investment на сумму золота, умноженную на цену золота в долларах за унцию.У меня есть сотрудник под названием PriceCalculator, который вычисляет цену золота. Я мог бы написать такой тест.

public void addGoldIncreasesInvestmentValueByPriceTimesAmount(){ 
    PriceCalculator mockCalculator = mock(PriceCalculator.class); 
    when(mockCalculator.getGoldPrice()).thenReturn(new BigDecimal(400)); 
    Investment toTest = new Investment(new BigDecimal(10000)); 
    toTest.addGold(5); 
    assertEquals(new BigDecimal(12000), toTest.getValue()); 
} 

В этом случае результат метода соавтора важен для испытания. Мы его заглушаем, потому что в данный момент мы не тестируем PriceCalculator. Нет необходимости проверять, потому что, если метод не был вызван, конечное значение инвестиционного значения было бы неверным. Так что все, что нам нужно, это assert.

Теперь предположим, что класс Investment уведомляет IRS, когда кто-либо снимает более 100000 долларов США с Investment. Для этого используется коллаборатор под названием IrsNotifier. Так что тест на это может выглядеть так.

public void largeWithdrawalNotifiesIRS(){ 
    IrsNotifier mockNotifier = mock(IrsNotifier.class); 
    Investment toTest = new Investment(new BigDecimal(200000)); 
    toTest.setIrsNotifier(mockNotifier); 
    toTest.withdraw(150000); 
    verify(mockNotifier).notifyIRS(); 
} 

В этом случае тест не заботится о возвращаемом значении методы сотрудника notifyIRS(). Или, может быть, это пустота. Важно то, что метод вызван. Для теста, подобного этому, вы будете использовать verify. В этом случае может произойти опечатка (настроить других коллабораторов или вернуть значения из разных методов), но маловероятно, что вы когда-либо захотите заглушить тот же метод, который вы проверите.

Если вы обнаружите, что используете оба этапа и проверку на одном и том же методе соавтора, вы должны, вероятно, спросить себя, почему. Какое испытание действительно пытается доказать? Соответствует ли возвращаемое значение тесту? Потому что это, как правило, запах кода тестирования.

Надеюсь, что эти примеры вам полезны.

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