В комментарии к этому замечательному сообщению Рой Ошеров упомянул проект OAPT , предназначенный для запуска каждого утверждения в одном тесте.
На домашней странице проекта написано следующее:
Правильные юнит-тесты должны проваливаться ровно по одной причине, поэтому вы должны использовать одно утверждение на юнит-тест.
И, кроме того, Рой написал в комментариях:
Моя рекомендация обычно заключается в том, что вы тестируете одну логическую КОНЦЕПЦИЮ на тест. Вы можете иметь несколько утверждений на один и тот же объект . они обычно будут той же самой концепцией, которая тестируется.
Я думаю, что в некоторых случаях требуется несколько утверждений (например, Guard Assertion ), но в целом я стараюсь этого избегать. Каково твое мнение? Пожалуйста, приведите пример из реальной жизни, где действительно нужно несколько утверждений .
источник
RowTest
(MbUnit) /TestCase
(NUnit) использовалось несколько утверждений для тестирования различных вариантов поведения в крайнем случае. Используйте соответствующие инструменты для работы! (К сожалению, MSTest, похоже, пока не имеет возможности для тестирования строк.)RowTest
иTestCase
использовать источники тестовых данных . Я использую простой CSV-файл с большим успехом.Ответы:
Я не думаю, что это обязательно плохо , но я думаю, что мы должны стремиться к тому, чтобы в наших тестах были только отдельные утверждения . Это означает, что вы пишете намного больше тестов, и наши тесты в конечном итоге будут тестировать только одну вещь за раз.
Сказав это, я бы сказал, что, возможно, половина моих тестов на самом деле имеет только одно утверждение. Я думаю, что это становится запахом кода (тест?), Когда у вас есть около пяти или более утверждений в вашем тесте.
Как вы решаете несколько утверждений?
источник
Тесты должны проваливаться только по одной причине, но это не всегда означает, что должно быть только одно
Assert
утверждение. ИМХО, важнее придерживаться модели « Аранжируй, действуй, утверждай ».Ключ заключается в том, что у вас есть только одно действие, а затем вы проверяете результаты этого действия, используя утверждения. Но это «Устройся, Действуй, Утверждай, Конец теста ». Если у вас возникнет желание продолжить тестирование, выполнив другое действие, а затем еще несколько утверждений, сделайте это отдельным тестом.
Я счастлив видеть несколько утверждений assert, которые образуют части тестирования одного и того же действия. например
или же
Вы можете объединить их в одно утверждение, но это не то же самое, что настаивать, что вы должны или должны . Нет никакого улучшения от их объединения.
например, первый может быть
Но это не лучше - сообщение об ошибке из него менее конкретное, и у него нет других преимуществ. Я уверен, что вы можете подумать о других примерах, где объединение двух или трех (или более) утверждений в одно большое логическое условие усложняет чтение, затрудняет изменение и усложняет задачу выяснения причины неудачи. Почему это делается только ради правила?
NB : код, который я пишу здесь, - это C # с NUnit, но принципы будут соответствовать другим языкам и фреймворкам. Синтаксис также может быть очень похожим.
источник
Assert.IsBetween(10, 100, value)
что печатаетExpected 8 to be between 10 and 100
это лучше , чем две отдельные утверждает , на мой взгляд. Вы, конечно, можете утверждать, что в этом нет необходимости, но обычно стоит подумать о том, легко ли свести его к одному утверждению перед тем, как создавать их целиком.Я никогда не думал, что более чем одно утверждение было плохой вещью.
Я делаю это все время:
Здесь я использую несколько утверждений, чтобы убедиться, что сложные условия можно превратить в ожидаемый предикат.
Я тестирую только одну единицу (
ToPredicate
метод), но покрываю все, что могу придумать в тесте.источник
Когда я использую модульное тестирование для проверки поведения высокого уровня, я просто помещаю несколько утверждений в один тест. Вот тест, который я на самом деле использую для какого-то кода экстренного уведомления. Код, который выполняется перед тестом, переводит систему в состояние, при котором при запуске основного процессора отправляется сигнал тревоги.
Он представляет условия, которые должны существовать на каждом этапе процесса, чтобы я был уверен, что код ведет себя так, как я ожидаю. Если одно утверждение не удастся, мне все равно, что остальные не будут запущены; поскольку состояние системы больше не является действительным, эти последующие утверждения не скажут мне ничего ценного. * В случае
assertAllUnitsAlerting()
неудачи я не буду знать, что делатьassertAllNotificationSent()
с успехом ИЛИ сбоем, пока не определю, что было причиной предыдущей ошибки. и исправил это.(* - Хорошо, они, возможно, могут быть полезны при устранении проблемы. Но самая важная информация о том, что тест не пройден, уже получена.)
источник
Другая причина, по которой я считаю, что несколько утверждений в одном методе - это неплохо, описана в следующем коде:
В моем тесте я просто хочу проверить, что
service.process()
возвращает правильное число вInner
экземплярах класса.Вместо тестирования ...
я делаю
источник
Assert.notNull
избыточны, ваш тест провалится с NPE, если они нулевые.if
) пройдет, еслиres
этоnull
Assert.assertEquals(..., service.process().getInner());
, возможно с извлеченными переменными, если строка становится «слишком длинной»Я думаю, что существует множество случаев, когда написание нескольких утверждений является допустимым в рамках правила, согласно которому тест должен проваливаться только по одной причине.
Например, представьте себе функцию, которая анализирует строку даты:
Если тест не пройден по одной причине, синтаксический анализ неверен. Если вы утверждаете, что этот тест может провалиться по трем различным причинам, вы ИМХО были бы слишком точны в своем определении «одной причины».
источник
Я не знаю ни одной ситуации, когда было бы неплохо иметь несколько утверждений внутри самого метода [Test]. Основная причина, по которой людям нравится иметь несколько утверждений, заключается в том, что они пытаются создать один класс [TestFixture] для каждого тестируемого класса. Вместо этого вы можете разбить свои тесты на несколько классов [TestFixture]. Это позволяет увидеть несколько способов, которыми код, возможно, не отреагировал так, как вы ожидали, а не тот, в котором первое утверждение не удалось. Способ достижения этого заключается в том, что у вас есть по крайней мере один каталог на класс, который тестируется с большим количеством классов [TestFixture] внутри. Каждый класс [TestFixture] будет назван в соответствии с конкретным состоянием объекта, который вы будете тестировать. Метод [SetUp] переводит объект в состояние, описываемое именем класса. Затем у вас есть несколько методов [Test], каждый из которых утверждает разные вещи, которые, как вы ожидаете, будут истинными, учитывая текущее состояние объекта. Каждый метод [Test] назван в честь того, что он утверждает, за исключением того, что, возможно, он может быть назван в честь концепции, а не только для чтения кода на английском языке. Тогда каждой реализации метода [Test] нужна только одна строка кода, в которой он что-то утверждает. Еще одним преимуществом этого подхода является то, что он делает тесты очень удобочитаемыми, поскольку становится совершенно ясно, что вы тестируете, и что вы ожидаете, просто взглянув на имена классов и методов. Это также будет лучше масштабироваться по мере того, как вы начнете понимать все мелкие крайние случаи, которые вы хотите протестировать, и по мере обнаружения ошибок. за исключением, возможно, он может быть назван в честь концепции, а не просто чтение кода на английском языке. Тогда каждой реализации метода [Test] нужна только одна строка кода, в которой он что-то утверждает. Еще одним преимуществом этого подхода является то, что он делает тесты очень удобочитаемыми, поскольку становится совершенно ясно, что вы тестируете, и что вы ожидаете, просто взглянув на имена классов и методов. Это также будет лучше масштабироваться по мере того, как вы начнете понимать все мелкие крайние случаи, которые вы хотите протестировать, и по мере обнаружения ошибок. за исключением, возможно, он может быть назван в честь концепции, а не просто чтение кода на английском языке. Тогда каждой реализации метода [Test] нужна только одна строка кода, в которой он что-то утверждает. Еще одним преимуществом этого подхода является то, что он делает тесты очень удобочитаемыми, поскольку становится совершенно ясно, что вы тестируете, и что вы ожидаете, просто взглянув на имена классов и методов. Это также будет лучше масштабироваться по мере того, как вы начнете понимать все мелкие крайние случаи, которые вы хотите протестировать, и по мере обнаружения ошибок. и что вы ожидаете, просто посмотрев на имена классов и методов. Это также будет лучше масштабироваться по мере того, как вы начнете понимать все мелкие крайние случаи, которые вы хотите протестировать, и по мере обнаружения ошибок. и что вы ожидаете, просто посмотрев на имена классов и методов. Это также будет лучше масштабироваться по мере того, как вы начнете понимать все мелкие крайние случаи, которые вы хотите протестировать, и по мере обнаружения ошибок.
Обычно это означает, что последняя строка кода внутри метода [SetUp] должна хранить значение свойства или возвращаемое значение в частной переменной экземпляра [TestFixture]. Тогда вы можете утверждать несколько разных вещей об этой переменной экземпляра из разных методов [Test]. Вы также можете сделать утверждение о том, какие различные свойства тестируемого объекта установлены сейчас, когда он находится в желаемом состоянии.
Иногда по ходу дела вам нужно делать утверждения, когда вы переводите тестируемый объект в желаемое состояние, чтобы убедиться, что вы не ошиблись, прежде чем перевести объект в желаемое состояние. В этом случае эти дополнительные утверждения должны появиться внутри метода [SetUp]. Если что-то пойдет не так внутри метода [SetUp], будет ясно, что что-то не так с тестом, прежде чем объект попадет в желаемое состояние, которое вы намеревались проверить.
Еще одна проблема, с которой вы можете столкнуться, заключается в том, что вы можете тестировать исключение, которое вы ожидали получить. Это может соблазнить вас не следовать приведенной выше модели. Тем не менее, это все еще может быть достигнуто путем перехвата исключения внутри метода [SetUp] и сохранения его в переменной экземпляра. Это позволит вам утверждать разные вещи об исключении, каждый в своем собственном методе [Test]. Затем вы можете также утверждать и другие вещи относительно тестируемого объекта, чтобы убедиться в отсутствии непреднамеренных побочных эффектов от создаваемого исключения.
Пример (это будет разбито на несколько файлов):
источник
[Test]
метода этот класс повторно создается, и[SetUp]
метод выполняется снова. Это убивает .NET Garbage Collector и заставляет тесты выполняться очень медленно: 5+ локально, 20+ минут на сервере сборки. Тесты 20К должны выполняться примерно через 2-3 минуты. Я бы вообще не рекомендовал этот стиль тестирования, особенно для большого набора тестов.Наличие нескольких утверждений в одном и том же тесте является проблемой только в случае неудачи теста. Тогда вам, возможно, придется отладить тест или проанализировать исключение, чтобы выяснить, какое утверждение является ошибочным. С одним утверждением в каждом тесте обычно легче определить, что не так.
Я не могу придумать сценарий, в котором действительно требуется несколько утверждений , поскольку вы всегда можете переписать их как несколько условий в одном утверждении. Однако может быть предпочтительнее, если, например, у вас есть несколько шагов для проверки промежуточных данных между шагами, а не рискуете, что последующие шаги потерпят крах из-за неправильного ввода.
источник
Если ваш тест не пройден, вы не будете знать, нарушатся ли следующие утверждения. Зачастую это означает, что вам не хватит ценной информации, чтобы выяснить источник проблемы. Мое решение заключается в использовании одного утверждения, но с несколькими значениями:
Это позволяет мне видеть все несостоятельные утверждения сразу. Я использую несколько строк, потому что большинство IDE будут отображать различия строк в диалоговом окне сравнения.
источник
Если у вас есть несколько утверждений в одной тестовой функции, я ожидаю, что они будут иметь непосредственное отношение к тесту, который вы проводите. Например,
Много тестов (даже если вы чувствуете, что это, вероятно, перебор) - это не плохо. Вы можете утверждать, что проведение жизненно важных и наиболее важных тестов является более важным. Поэтому, когда вы утверждаете, убедитесь, что ваши утверждения assert правильно размещены, а не слишком беспокоитесь о множественных утверждениях. Если вам нужно более одного, используйте более одного.
источник
Цель юнит-теста состоит в том, чтобы дать вам как можно больше информации о том, что не работает, а также помочь в первую очередь точно определить наиболее фундаментальные проблемы. Когда вы логически знаете, что одно утверждение потерпит неудачу, учитывая, что другое утверждение не выполнено, или, другими словами, между тестами существует зависимость, тогда имеет смысл свернуть их как несколько утверждений в одном тесте. Преимущество этого заключается в том, что результаты теста не засоряются очевидными ошибками, которые можно было бы устранить, если бы мы выручили первое утверждение в рамках одного теста. В случае, когда такого отношения не существует, естественным образом предпочтение будет отдавать этим утверждениям на отдельные тесты, потому что в противном случае для обнаружения этих отказов потребовалось бы несколько итераций прогонов тестов для решения всех проблем.
Если вы затем также спроектируете модули / классы таким образом, что потребуется написать слишком сложные тесты, это сделает меньше нагрузки во время тестирования и, вероятно, будет способствовать лучшему дизайну.
источник
Да, можно иметь несколько утверждений, если неудачный тест дает вам достаточно информации для диагностики сбоя. Это будет зависеть от того, что вы тестируете и каковы режимы сбоя.
Я никогда не находил такие формулировки полезными (то, что у класса должна быть одна причина для изменения, является примером такой бесполезной пословицы). Рассмотрим утверждение, что две строки равны, это семантически эквивалентно утверждению, что длина двух строк одинакова, и каждый символ в соответствующем индексе равен.
Мы могли бы обобщить и сказать, что любая система множественных утверждений может быть переписана как одно утверждение, и любое отдельное утверждение может быть разложено в набор меньших утверждений.
Итак, просто сосредоточьтесь на ясности кода и ясности результатов теста, и пусть это будет определять количество утверждений, которые вы используете, а не наоборот.
источник
Ответ очень прост - если вы тестируете функцию, которая изменяет более одного атрибута, одного и того же объекта или даже двух разных объектов, и правильность функции зависит от результатов всех этих изменений, то вы хотите утверждать что каждое из этих изменений было выполнено правильно!
Я получаю представление о логической концепции, но обратный вывод сказал бы, что никакая функция никогда не должна изменять более одного объекта. Но это невозможно реализовать во всех случаях, по моему опыту.
Возьмите логическую концепцию банковской транзакции - снятие суммы с одного банковского счета в большинстве случаев ДОЛЖНО включать добавление этой суммы на другой счет. Вы НИКОГДА не хотите отделить эти две вещи, они образуют атомную единицу. Возможно, вы захотите сделать две функции (снять / добавить деньги) и, таким образом, написать два разных модульных теста - в дополнение. Но эти два действия должны выполняться в рамках одной транзакции, и вы также хотите убедиться, что транзакция работает. В этом случае просто недостаточно убедиться, что отдельные шаги были успешными. Вы должны проверить оба банковских счета, в вашем тесте.
Вначале могут быть более сложные примеры, которые вы бы не тестировали в модульном тесте, а вместо этого в интеграционном или приемочном тесте. Но эти границы свободно, ИМХО! Это не так легко решить, это вопрос обстоятельств и, возможно, личных предпочтений. Снятие денег с одного и добавление их на другой счет - все еще очень простая функция, и, безусловно, кандидат на юнит-тестирование.
источник
Этот вопрос связан с классической проблемой баланса между проблемами кода спагетти и лазаньи.
Наличие нескольких утверждений может легко привести к проблеме спагетти, когда у вас нет представления о том, что представляет собой тест, но наличие одного подтверждения на тест может сделать ваше тестирование одинаково нечитаемым, имея несколько тестов в большой лазаньи, что делает поиск того, что тест делает невозможным ,
Есть некоторые исключения, но в этом случае удержание маятника посередине является ответом.
источник
Я даже не согласен с «провалом только по одной причине» в целом. Что более важно, так это то, что тесты короткие и четко читаются на ИМО.
Это не всегда достижимо, хотя и когда тест сложен, (длинное) описательное имя и проверка меньшего количества вещей имеют больше смысла.
источник