Тестирование: детерминированное или недетерминированное?

16

Это лучше иметь

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

?

Пример: вы пишете набор тестов для тестирования функциональности контроллера в приложении MVC. Контроллер требует данные приложения из базы данных в качестве входных данных во время теста. Есть два варианта сделать это:

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

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

Каковы причины, чтобы выбрать один над другим?

DCKing
источник
5
Этот тест иногда не проходит. martinfowler.com/articles/nonDeterminism.html
Спасибо за эту ссылку. Имея в виду эту статью, я почувствовал необходимость пояснить, что означает недетерминизм в контексте этого набора тестов. Поскольку данные выбираются случайным образом из базы данных, все данные, поступающие в контроллер, по умолчанию являются действительными данными. Это означает, что ложных негативов в тестовом наборе не существует, когда дело доходит до недетерминизма. В некотором смысле эта случайность моделирует пользователя, выбирающего данные «в произвольном порядке» для использования в контроллере. Это не обязательно тот же недетерминизм, который обсуждается в статье, верно?
DCKing
10
@DCKing: подумайте, что произойдет, если ваш тест не пройден. Хорошо, у вас есть ошибка. Ну и что теперь? Запустите его снова в режиме отладки! Где это удается! Как это происходит в следующую сотню раз, когда вы запускаете его, а затем списываете проблему как удар космического луча. Неопределенность в тестах звучит абсолютно неосуществимо. Если в ваших тестовых примерах вы чувствуете необходимость покрыть больше земли, покрывайте больше земли. Инициализируйте свой ГСЧ с заданным начальным числом и выполните «тест» несколько сотен раз с постоянно случайными значениями.
Phoshi
1
(наконец-то добрался до машины, где я мог правильно искать в твиттере - « Этот тест иногда проваливается » взят из #FiveWordTechHorrors в Твиттере - хотел правильно его

Ответы:

30

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

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

РЕДАКТИРОВАТЬ: Использование генератора случайных чисел для выбора некоторых тестовых данных ИМХО иногда является признаком того, что слишком ленив в выборе хороших тестовых данных. Вместо того, чтобы выбрасывать 100 000 случайно выбранных тестовых значений и надеяться, что этого будет достаточно, чтобы случайно обнаружить все серьезные ошибки, лучше использовать свой мозг, выбрать от 10 до 20 «интересных» случаев и использовать их для набора тестов. Это приведет не только к повышению качества ваших тестов, но и к гораздо более высокой производительности пакета.

Док Браун
источник
Спасибо за Ваш ответ. Каково ваше мнение относительно комментария, который я сделал к своему вопросу?
DCKing
1
@DCKing: если вы действительно думаете, что генератор случайных чисел будет лучше выбирать хорошие тестовые случаи, чем вы (в чем я сомневаюсь), используйте его один раз, чтобы найти комбинации тестовых данных, в которых ваша программа потерпит неудачу, и поместите эти комбинации в «жестко закодированную» часть вашего набора тестов.
Док Браун
Еще раз спасибо. Обновил мой ответ, чтобы он не относился только к приложениям MVC.
DCKing
1
В некоторых контекстах пользовательского интерфейса (например, в играх, использующих ввод с контроллера) наличие тестовых программ, генерирующих случайный ввод ключа, может быть полезно для стресс-тестирования. Они могут обнаружить дефекты, которые трудно найти с помощью преднамеренного ввода.
Gort the Robot
@ StevenBurnap: хорошо, насколько я понимаю вопрос, я думаю, что OP имел в виду более обычное регрессионное тестирование. Конечно, я согласен, стресс-тестирование - это особый случай, который также может зависеть от аппаратного обеспечения и приводить к недетерминированному поведению, даже если вы не используете генератор случайных чисел. Это то, что описано в статье, на которую ссылается MichaelT в первом комментарии под вопросом. И даже в стресс-тестировании со случайным вводом можно, по крайней мере, попытаться сделать поведение более детерминированным, используя определенное случайное начальное число.
Док Браун
4

И детерминированный и недетерминированный имеют место

Я бы разделил их следующим образом:

Модульные тесты.

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

Функциональные и входные стресс-тесты.

Они могут использовать недетерминированный подход со следующими оговорками:

  • этот факт четко обозначен и вызван
  • выбранные случайные значения регистрируются и могут быть повторены вручную
Майкл Даррант
источник
3

И то и другое.

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

Давайте рассмотрим пример: вы пишете функцию, которая сортирует список целых чисел. Какие бы детерминированные юнит-тесты вы бы нашли полезным?

  • Пустой список
  • Список только с одним элементом
  • Список со всеми одинаковыми элементами
  • Список с несколькими уникальными элементами
  • Список с несколькими элементами, некоторые из которых являются дубликатами
  • Список с NaN, INT_MINиINT_MAX
  • Список, который уже частично отсортирован
  • Список с 10 000 000 элементов

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

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

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

Обратите внимание, что это означает, что много твердых советов о модульных тестах не обязательно относятся к недетерминированным тестам. Например, что они должны быть быстрыми. Низкоуровневые тесты свойств должны быть быстрыми, но недетерминированный тест, такой как «симуляция случайного нажатия пользователем кнопок на вашем веб-сайте и проверка того, что вы никогда не получите ошибку 500», должен способствовать полноте над скоростью. Просто запустите такой тест независимо от процесса сборки, чтобы он не замедлял разработку. Например, запустите его в своем личном инсталляционном окне.

Hovercouch
источник
-1

Вы действительно не хотите детерминированного против недетерминированного.

То, что вы можете хотеть, это «всегда одно и то же» против «не всегда одно и то же».

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

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

gnasher729
источник
1
Или, если у вас нет номера сборки для использования, поместите начальное значение начального числа в выходные данные тестового прогона, чтобы вы могли снова запустить тесты с тем же начальным числом.
RemcoGerlich