Модульное тестирование нескольких условий в выражении IF

26

У меня есть кусок кода, который выглядит примерно так:

function bool PassesBusinessRules()
{
    bool meetsBusinessRules = false;

    if (PassesBusinessRule1 
         && PassesBusinessRule2
         && PassesBusinessRule3)
    {
         meetsBusinessRules= true;
    }

    return meetsBusinessRules;
}

Я считаю, что должно быть четыре модульных теста для этой конкретной функции. Три, чтобы проверить каждое из условий в операторе if и убедиться, что оно возвращает false. И еще один тест, который проверяет, что функция возвращает true.

Вопрос: Должно ли на самом деле быть десять юнит-тестов? Девять, которая проверяет каждый из возможных путей отказа. IE:

  • Ложь Ложь Ложь
  • Ложь Ложь Истина
  • Ложь верно ложь

И так далее для каждой возможной комбинации.

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

bwalk2895
источник
Использует ли компилятор жадные вычисления для оператора &&?
Suszterpatt
12
Если вы написали 10 модульных тестов, вы будете тестировать оператор &&, а не свои методы.
Мерт Акчакая
2
Разве не было бы только восьми тестов, если бы вы проверили все возможные комбинации? Три логических параметра включены или выключены.
Крис Харпер
3
@Mert: Только если вы можете гарантировать, что && всегда будет там.
Миско
Хикки: Если мы тратим его на написание тестов, то мы не тратим время на что-то еще. Каждый из нас должен оценить, как лучше всего проводить время, чтобы максимизировать наши результаты, как по количеству, так и по качеству. Если люди думают, что тратить пятьдесят процентов своего времени на написание тестов максимизируют свои результаты - хорошо для них. Я уверен, что это не так для меня - я бы скорее потратил это время на размышления о своей проблеме. Я уверен, что для меня это дает лучшие решения с меньшим количеством дефектов, чем любое другое использование моего времени. Плохой дизайн с полным набором тестов все еще плохой дизайн.
Работа

Ответы:

29

Формально эти типы покрытия имеют имена.

Во-первых, есть предикатное покрытие : вы хотите иметь тестовый пример, который делает оператор if истинным, и такой, который делает его ложным. Выполнение этого покрытия, вероятно, является основным требованием для хорошего набора тестов.

Затем есть условие покрытия : здесь вы хотите проверить, что каждое подусловие в if имеет значение true и false. Это, очевидно, создает больше тестов, но, как правило, выявляет больше ошибок, поэтому, если у вас есть время, часто рекомендуется включать их в свой набор тестов.

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

Это лучше, чем простой предикат или условие покрытия? С точки зрения покрытия, конечно. Но это не бесплатно. Это очень дорого обходится при проведении испытаний. По этой причине большинство людей не беспокоятся о полном комбинаторном охвате. Обычно тестирование всех веток (или всех условий) будет достаточно для выявления ошибок. Добавление дополнительных тестов для комбинаторного тестирования обычно не выявляет больше ошибок, но требует много усилий для создания и поддержки. Дополнительные усилия, как правило, делают это не стоящим очень-очень небольшой отдачи, поэтому я бы не рекомендовал это.

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

Oleksi
источник
2
Если булевы значения передаются из внешних источников (то есть они не всегда проверяются), то часто необходимо комбинаторное условное покрытие. Сначала составьте таблицу комбинаций. Затем для каждой записи решите, представляет ли эта запись значимый вариант использования. Если нет, то где-то должен быть код (утверждение программного обеспечения или условие проверки), чтобы предотвратить выполнение этой комбинации. Важно не смешивать все параметры в одном комбинаторном тесте: попробуйте разделить параметры на группы, которые взаимодействуют друг с другом, то есть разделяют одно и то же логическое выражение.
Рулон
Насколько вы уверены в смелых терминах? Похоже, ваш ответ является единственным случаем «покрытия комбинаторных состояний», и некоторые источники говорят, что «предикатное покрытие» и «условное покрытие» - это одно и то же.
Стейн
8

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

Время для написания юнит-тестов обычно ограничено Поэтому постарайтесь найти лучший способ провести ограниченное время, которое у вас есть . Например, если у вас есть другой важный метод с охватом 0%, может быть лучше написать пару модульных тестов, чтобы охватить этот, а не добавлять дополнительные тесты для этого метода. Конечно, это также зависит от того, насколько хрупкой является реализация каждого из них. Планирование большого количества изменений этого конкретного метода в обозримом будущем может оправдать дополнительное покрытие модульных тестов. Так может быть на критическом пути внутри программы. Это все факторы, которые могут оценить только вы (команда).

Я лично был бы счастлив с 4 тестами, которые вы наметили, а именно:

  • правда ложь ложь
  • ложь правда ложь
  • ложь ложь правда
  • правда правда правда

плюс может быть один:

  • правда правда ложь

чтобы гарантировать, что единственный способ получить возвращаемое значение true- это выполнить все 3 бизнес-правила. Но, в конце концов, если ваши товарищи по команде настаивают на том, чтобы комбинаторные пути были покрыты, может быть дешевле добавить эти дополнительные тесты, чем продолжать спор намного дольше :-)

Петер Тёрёк
источник
3

Если вы хотите быть в безопасности, вам понадобится восемь модульных тестов с использованием условий, представленных таблицей истинности с тремя переменными ( http://teach.valdosta.edu/plmoch/MATH4161/Spring%202004/and_or_if_files/image006.gif ).

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

smp7d
источник
2
Модульное тестирование - тестирование белого ящика.
Петер Тёрёк
Ну, порядок не должен иметь значения, && является коммуникативным или, по крайней мере, должен быть
Захари К
2

Да, в идеальном мире должна быть полная комбинация.

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

Telastyn
источник
1
Модульное тестирование - это тестирование белого ящика. И мы не живем в идеальном мире.
Петер Тёрёк
@ PéterTörök - Мы не живем в идеальном мире, чтобы быть уверенным, но стек обмена не согласен с вами по другому вопросу. Специально для TDD тесты записываются в спецификации, а не в реализацию. Я лично принимаю «спецификацию», чтобы включить все входные данные (включая переменные-члены) и все выходные данные (включая побочные эффекты).
Теластин
1
Это только один конкретный поток в StackOverflow, касающийся конкретного случая, который не следует переоценивать. Тем более, что этот пост явно посвящен тестированию кода, который уже написан.
Петер Тёрёк
1

Государство это зло. Следующая функция не нуждается в модульном тестировании, потому что она не имеет побочных эффектов и хорошо понимает, что она делает, а что нет. Зачем это проверять? Ты не доверяешь своему мозгу ??? Статические функции великолепны!

static function bool Foo(bool a, bool b, bool c)
{
    return a && b && c;
}
работа
источник
2
Нет, я не доверяю своему собственному мозгу - я научился трудному способу всегда перепроверять, что я делаю :-) Так что мне все еще нужны юнит-тесты, чтобы убедиться, что я, например, ничего не набрал, и никто не собирается сломать код в будущем. И еще модульные тесты для проверки метода вызывающего, который вычисляет состояние, представленное a, bи c. Вы можете перемещать бизнес-логику по своему усмотрению, в конце концов вам все равно нужно где-то ее протестировать.
Петер Тёрёк
@ Péter Török, вы также можете вносить опечатки в свои тесты и, таким образом, получать ложные срабатывания, так где же остановиться? Вы пишете юнит-тесты для своих юнит-тестов? Я тоже не доверяю своему мозгу на 100%, но в конце дня написание кода - это то, чем я зарабатываю на жизнь. В этой функции может быть ошибка, но важно писать код таким образом, чтобы можно было легко отследить ошибку до исходного кода и чтобы после того, как вы изолировали проблему и исправили ее, вам лучше , Хорошо написанный код может опираться на интеграционные тесты, в основном infoq.com/presentations/Simple-Made-Easy
Job
2
Действительно, тесты тоже могут быть ошибочными. (TDD решает эту проблему, сначала проваливая тесты.) Однако повторение одного и того же вида ошибки дважды (и ее игнорирование) имеет гораздо меньшую вероятность. В целом, никакое количество и тип тестирования не могут доказать, что программное обеспечение не содержит ошибок, просто уменьшите вероятность ошибок до приемлемого уровня. И по скорости отслеживания ошибок в источнике, IMO ничто не может сравниться с юнит-тестами - быстрая обратная связь rulez :-)
Péter Török
«Следующая функция не нуждается в модульном тесте». Я думаю, вы здесь саркастичны, но это не ясно. Я доверяю своему мозгу? НЕТ! Доверяю ли я мозгу следующего парня, который касается кода? ДАЖЕ БОЛЬШЕ НЕТ! Верю ли я, что все предположения, лежащие в основе кода, будут верны через год? ... ты понимаешь мой дрейф Кроме того, статические функции убивают OO ... если вы хотите использовать FP, используйте язык FP.
Роб
1

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

Во-первых, ваши юнит-тесты должны иметь две цели:

  1. Создайте документацию для вас и ваших товарищей по команде, чтобы через некоторое время вы могли прочитать модульный тест и убедиться, что вы понимаете what's the class' intentionиhow the class is doing its work
  2. При разработке модульный тест проверяет, что код, который мы пишем, работает так, как задумано.

Итак, подытоживая проблему, мы хотим протестировать complex if statement, для данного примера есть 2 ^ 3 возможности, то есть большое количество тестов, которые мы можем написать.

  • Вы можете приспособиться к этому факту и записать 8 тестов или использовать параметризованные тесты.
  • Вы также можете следить за другими ответами и помнить, что тесты должны быть ясными с намерением, таким образом, мы не собираемся связываться со слишком многими деталями, которые в ближайшем будущем могут затруднить понимание what is doing the code

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

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

If some simple business rule apply, derive to the next handler

Насколько просто было бы протестировать различные простые правила вместо сложных правил?

Надеюсь, это поможет,

cnexans
источник
0

Это один из тех случаев, когда что-то вроде quickcheck ( http://en.wikipedia.org/wiki/QuickCheck ) станет вашим другом. Вместо того чтобы писать все N случаев вручную, компьютер сгенерирует все (или, по крайней мере, большое количество) возможных тестовых случаев и подтвердит, что все они дают разумный результат.

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

Захари К
источник
0

Вы можете изменить условия в охранные условия:

if (! PassesBusinessRule1) {
    return false;
}

if (! PassesBusinessRule2) {
    return false;
}

if (! PassesBusinessRule3) {
    return false;
}

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

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

обкрадывать
источник