Расщепление юнит-тестов по требованию или методу

16

Во-первых, извиняюсь за название, я не мог придумать самый простой способ объяснить это!

У меня есть метод, для которого я хочу написать модульные тесты. Я собираюсь оставить его достаточно общим, так как я не хочу обсуждать реализацию метода, только его тестирование. Метод таков:

public void HandleItem(item a)
{         
     CreateNewItem();
     UpdateStatusOnPreviousItem();
     SetNextRunDate();
}

Так что у этого класса есть один открытый метод, который затем вызывает некоторые частные методы для выполнения логики.

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

public void GivenItem_WhenRun_Thenxxxxx
{
     HandleItem(item);
     // Assert item has been created
     // Assert status has been set on the previous item
     // Assert run date has been set
}

Но я подумал, что я мог бы написать это как три отдельных теста:

public void GivenItem_WhenRun_ThenItemIsCreated()
{
    HandleItem(item);
}

public void GivenItem_WhenRun_ThenStatusIsUpdatedOnPreviousItem()
{
   HandleItem(item);
}

public void GivenItem_WhenRun_ThenRunDateIsSet()
{
     HandleItem(item);
}

Так что мне это кажется более приятным, так как это, по сути, перечисление требований, но тогда все три взаимосвязаны и требуют точно такой же работы, выполненной на тестируемом методе, поэтому я выполняю один и тот же код 3 раза.

Есть ли рекомендуемый подход с этим?

Благодарность

ADringer
источник

Ответы:

29

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

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

  • однако, если провал первого теста всегда будет означать, что два других теста также потерпят неудачу, то, вероятно, лучше использовать первый подход (так как не имеет смысла запускать тест, если вы уже знаете заранее, он будет провал).

Док Браун
источник
2
+1, хороший момент. Мне не приходило в голову, что время сборки также может быть узким местом.
Килиан Фот
1
@KilianFoth: Вы недостаточно часто работаете в C ++ :(
Матье М.
1
@MatthieuM .: честно говоря, вопрос помечен "C #"
Док Браун
10

Короткий ответ: гораздо важнее, чтобы ваши тесты охватывали всю функциональность, а не как они это делают.

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

  • удобочитаемость: если метод делает много вещей, которые не тесно связаны, комбинированный тест может быть трудным для понимания. Однако сам метод также может быть трудным для понимания, поэтому, возможно, вам следует реорганизовать метод, а не тест!)
  • эффективность: если выполнение метода занимает много времени, это может быть слабой причиной для объединения всех трех проверок для экономии времени
  • эффективность2: если выполнение кода установки вашей платформы занимает много времени, это также может быть слабой причиной для отказа от нескольких методов тестирования. (Однако, если это действительно проблема, вам, вероятно, следует исправить или изменить настройку теста - регрессионные тесты теряют большую часть своего значения, если вы не можете запустить их молниеносно.)
Килиан Фот
источник
2

Используйте один вызов метода с несколькими утверждениями. Вот почему:

Когда вы тестируете HandleItem (a), вы проверяете, что метод перевел элемент в правильное состояние. Вместо «одно утверждение на тест», подумайте «одна логическая концепция на тест».

Вопрос: Если CreateNewItem завершается неудачно, но другие два метода успешны, означает ли это, что HandleItem завершился успешно? Я думаю, нет.

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

ИМО, эти вопросы обычно являются признаком чего-то другого. Это признак того, что HandleItem не является чем-то, что вы можете «модульно протестировать», так как кажется, что оно просто делегирует другим методам. Когда вы просто проверяете, что HandleItem правильно вызывает другие методы, он становится скорее кандидатом в интеграционный тест (в этом случае у вас все еще будет 3 утверждения).

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

Kevin
источник
0

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

Eternal21
источник
-1

ИМХО, вы должны протестировать три части этого метода отдельно, чтобы вы более точно знали, где что-то идет не так, когда они делают, избегая при этом повторения одной и той же части вашего кода дважды.

суперпользователя
источник
-2

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

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

IllusiveBrian
источник