Тестирование списка ... Все в одном тесте или один тест для каждого условия?


21

Я проверяю, что функция делает то, что ожидалось в списке. Итак, я хочу проверить

f(null) -> null
f(empty) -> empty
f(list with one element) -> list with one element
f(list with 2+ elements) -> list with the same number of elements, doing what expected

Для этого, каков наилучший подход?

  • Тестирование всех случаев в одном (методическом) тесте под названием «WorksAsExpected»
  • Размещение одного теста для каждого случая, таким образом, имея
    • "WorksAsExpectedWhenNull"
    • "WorksAsExpectedWhenEmpty"
    • "WorksAsExpectedWhenSingleElement"
    • "WorksAsExpectedWhenMoreElements"
  • Другой выбор, о котором я не думал :-)


2
Я бы написал их как отдельные контрольные примеры. Вы можете использовать параметризованные тесты, если ваша тестовая система поддерживает это.
Джоншарп

5
Если вы пишете свои тесты в заданном ... когда ... тогда стиле, тогда становится самоочевидным, что они действительно должны тестироваться отдельно ...
Робби Ди,

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

1
Список с повторяющимися элементами?
Атайенел

Ответы:


30

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

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

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

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


14

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

Возможные инструменты, основанные на вашей структуре:

  • Теории . Теория позволяет вам проверить ряд фактов о наборе данных. Затем инфраструктура будет снабжать ваши тесты несколькими сценариями тестовых данных - либо полем, либо статическим методом, который генерирует данные. Если некоторые из ваших фактов применимы на основе одних предварительных условий, а другие - нет, эти рамки вводят концепцию предположения . Вы Assume.that()просто пропускаете тест для данных, если он не проходит предварительное условие. Это позволяет вам определить «Работает как положено», а затем просто передать ему много данных. Когда вы просматриваете результаты, у вас есть запись для родительских тестов, а затем дополнительная запись для каждого фрагмента данных.
  • Параметризованные тесты . Параметризованные тесты были предшественниками теорий, поэтому может не быть той проверки предусловий, которую вы можете проводить с теориями. Конечный результат такой же. Если вы просматриваете результаты, у вас есть родительская запись для самого теста, а затем конкретная запись для каждой точки данных.
  • Один тест с несколькими утверждениями . На настройку уходит меньше времени, но вы в конечном итоге обнаруживаете проблемы за раз. Если тест слишком длинный и тестируется слишком много различных сценариев, существует два больших риска: его выполнение займет много времени, и ваша команда сыт по горло и отключит тест.
  • Несколько тестов с аналогичной реализацией . Важно отметить, что если утверждения различны, они не перекрываются. Однако это было бы общепринятым мнением команды, ориентированной на TDD.

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

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


5

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

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

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

Довольно другой подход - тестирование свойств . Вы должны создать различные (параметризованные) тесты, которые утверждают различные свойства вашей функции, без указания конкретных входных данных. Например, свойство может быть: для всех списков длина xsсписка ys = f(xs)равна xs. Среда тестирования будет генерировать интересные списки и случайные списки и утверждать, что ваши свойства сохраняются для всех из них. Это отходит от ручного задания примеров, поскольку при ручном выборе примеров могут пропустить интересные крайние случаи.


Разве «мисс» в последнем предложении не должна быть «найти»?
Робби Ди

@RobbieDee Английский язык неоднозначен, исправлен.
Amon

3

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

Смотрите этот пост: нормально ли иметь несколько утверждений в одном модульном тесте? , Там также есть актуальное и подробное обсуждение:

Моя рекомендация обычно заключается в том, что вы тестируете одну логическую КОНЦЕПЦИЮ на тест. Вы можете иметь несколько утверждений на один и тот же объект. они обычно будут той же самой концепцией, которая тестируется. Источник - Рой Ошерове

[...]

Тесты должны проваливаться только по одной причине, но это не всегда означает, что должен быть только один оператор Assert. ИМХО, важнее придерживаться модели «Аранжируй, действуй, утверждай».

Ключ заключается в том, что у вас есть только одно действие, а затем вы проверяете результаты этого действия, используя утверждения. Но это «Устройся, Действуй, Утверждай, Конец теста». Если у вас возникнет желание продолжить тестирование, выполнив другое действие, а затем еще несколько утверждений, сделайте это отдельным тестом. Источник


0

На мой взгляд, это зависит от условий испытаний.

  • Если ваш тест имеет только 1 условие для настройки теста, но много побочных эффектов. Multi-Assert является приемлемым.
  • Но если у вас несколько условий, это означает, что у вас есть несколько тестовых наборов, каждый из которых должен быть покрыт только 1 модульным тестом.

это больше похоже на комментарий, см. Как ответить
gnat
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.