Есть ли лучший способ проверить следующие методы, не возвращая mocks? - PullRequest
4 голосов
/ 05 февраля 2012

Примите следующие настройки:

interface Entity {}

interface Context { 
     Result add(Entity entity);
}

interface Result {
     Context newContext();
     SpecificResult specificResult();
}

class Runner {

    SpecificResult actOn(Entity entity, Context context) {
           return context.add(entity).specificResult();
    }
}

Я хочу видеть, что метод actOn просто добавляет сущность в контекст и возвращает конкретный результат. Сейчас я проверяю это следующим образом (используя Mockito)

@Test
public void testActOn() {
    Entity entity = mock(Entity.class);
    Context context = mock(Context.class);
    Result result = mock(Result.class);
    SpecificResult specificResult = mock(SpecificResult.class);
    when(context.add(entity)).thenReturn(result);
    when(result.specificResult()).thenReturn(specificResult);
    Assert.assertTrue(new Runner().actOn(entity,context) == specificResult);
}

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

Поскольку люди запрашивали больше контекста, исходной проблемой является абстракция DFS, в которой контекст собирает элементы графа и вычисляет результаты, которые сопоставляются и возвращаются. ActOn на самом деле действие на листьях.

Ответы [ 6 ]

3 голосов
/ 05 февраля 2012

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

Так что в вашем контракте, что вы хотите протестировать наactOn method:

  • То, что он возвращает SpecificResult с учетом Context и Entity
  • То, что add(), specificResult() взаимодействия происходят соответственноContext и Entity
  • То, что SpecificResult - это один и тот же экземпляр, возвращаемый Result
  • и т. д.

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

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

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

Context context = mock(Context.class, RETURNS_DEEP_STUBS);
given(context.add(any(Entity.class)).specificResult()).willReturn(someSpecificResult);

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

Другие замечания:

  • Имя вашего метода тестирования недостаточно точное testActOn сообщает читателю, какое поведение вы тестируете.Обычно практикующие специалисты заменяют название метода контрактным предложением, таким как returns_a_SpecificResult_given_both_a_Context_and_an_Entity, которое является явно более читабельным и дает практикующему объем того, что тестируется.

  • Вы создаете фиктивные экземпляры в тесте с синтаксисом Mockito.mock(), если у вас есть несколько подобных тестов, я бы порекомендовал вам использовать MockitoJUnitRunner с аннотациями @Mock, это немного загромождает ваш код и позволяетчитатель, чтобы лучше увидеть, что происходит в этом конкретном тесте.

  • Используйте подход BDD (Behavior Driven Dev) или AAA (Arrange Act Assert).

Например:

@Test public void invoke_add_then_specificResult_on_call_actOn() {
    // given
    ... prepare the stubs, the object values here

    // when
    ... call your production code

    // then
    ... assertions and verifications there
}

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

Здесь и там много чтения на тестах, Мартин Фаулер имеет очень хорошие статьи по этому вопросу, Джеймс Карр составил список тестирование анти-паттернов , также много читают о том, как правильно использовать макеты (например, не имитируют типы, которыми вы не владеете mojo), соавтор Nat Pryceиз Растущее объектно-ориентированное программное обеспечение, основанное на тестах , который, на мой взгляд, необходимо прочитать, плюс у вас есть Google;)

1 голос
/ 06 февраля 2012

Мне нравится использовать имена, начинающиеся с mock для всех моих фиктивных объектов.Кроме того, я бы заменил

 when(result.specificResult()).thenReturn(specificResult); 
 Assert.assertTrue(new Runner().actOn(entity,context) == specificResult); 

на

Runner toTest = new Runner();
toTest.actOn( mockEntity, mockContext );
verify( mockResult ).specificResult();

, потому что все, что вы пытаетесь утверждать, это то, что specificResult() запускается на правильном фиктивном объекте.В то время как ваше первоначальное утверждение не совсем ясно дает понять, что утверждается.Таким образом, вам на самом деле не нужно издеваться над SpecificResult.Это сокращает вас до одного when звонка, что мне кажется правильным для такого рода испытаний.

Но да, это действительно ужасно белая коробка.Является ли Runner общедоступным классом или скрытой деталью реализации процесса более высокого уровня?Если это последнее, то вы, вероятно, захотите написать тесты вокруг поведения на более высоком уровне;вместо изучения деталей реализации.

1 голос
/ 05 февраля 2012

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

0 голосов
/ 06 февраля 2012

Прежде всего, так как никто не упомянул об этом, Mockito поддерживает цепочку, поэтому вы можете просто сделать:

when(context.add(entity).specificResult()).thenReturn(specificResult);

(и см. Комментарий Брайса о том, как это сделать, извините, я пропустил это!)

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

Непонятно, почему Бегун должен получить SpecificResult, а не какой-либо другой результат, полученный из context.add(entity), поэтому я собираюсь сделать предположение: Result содержит результат с некоторыми сообщениями или другими информация, и вы просто хотите знать, является ли это успехом или неудачей.

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

Если некоторые классы регулярно используют ваш Runner только для того, чтобы получить конкретный результат, в то время как другим требуется дополнительная обратная связь, я бы сделал два метода для этого, например, с именами add и addWithFeedback, так же как Amazon Вы совершаете покупки в один клик по другому маршруту.

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

0 голосов
/ 05 февраля 2012

Хотя контекст предоставит больше информации, я не вижу никаких проблем с вашей методологией тестирования.Весь смысл фиктивных объектов заключается в проверке поведения при вызове без необходимости реализации реализаций.Создание объектов-заглушек или использование реальных реализующих классов просто кажется мне ненужным.

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

0 голосов
/ 05 февраля 2012

Не зная много о контексте кода, я бы предположил, что Context и Result - это, вероятно, простые объекты данных с очень небольшим поведением. Вы можете использовать Fake, как предложено в другом ответе, или, если у вас есть доступ к реализациям этих интерфейсов, а конструкция проста, я бы просто использовал реальные объекты вместо Fakes или Mocks.

...