Значение проверки поведения - PullRequest
5 голосов
/ 27 декабря 2011

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

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

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

Сейчас я пытаюсь активно использовать mocks в моем отряде.тесты.Mockito позволяет и заглушки и насмешки.Допустим, у нас есть объект Car, у которого есть метод getMaxSpeed().В Mockito мы могли бы заглушить его так:

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

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

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

Я понимаю, что застрял в " ошеломляющем состоянии ума ", и яНахожу невозможным сломаться.Все это чтение, и все это волнение, накапливающееся от использования насмешек в моем модульном тестировании и ... Я не могу придумать ни одного варианта использования для проверки поведения.

Так что я сделал резервную копию и перечитал Фаулера статья и другая литература в стиле BDD, и все же я просто "не понимаю" значения проверки поведения для тестовых двойных соавторов.

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

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

Ответы [ 3 ]

2 голосов
/ 27 декабря 2011

Мне нравится ответ @JB Nizet, но вот еще один пример.Предположим, вы хотите сохранить Car в базе данных с помощью 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 будет правильной в любом случае.

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

2 голосов
/ 27 декабря 2011

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

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);
}

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

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

Пример:

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);
    }
}
1 голос
/ 28 декабря 2011

Мое предположение заключается в том, что каждый контрольный пример должен содержать ЛИБО

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

но не оба.

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

Предположим, у меня есть класс 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. В таком тесте может быть заглушка (для настройки других соавторов или возврата значений из других методов), но маловероятно, что вам когда-нибудь захочется заглушить тот же метод, который вы проверяли.

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

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

...