Подтвердить что ты не робот

Что использовать тестирование свойств для

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

f :: [Integer] -> [Integer]

Эта функция, f, принимает список чисел и будет квадратировать нечетные числа и отфильтровать четные числа. Я могу указать некоторые свойства функции, например

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

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

f = fmap (+2) . filter odd

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

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

f ([2,5]) == [25]
f (-8,-3,11,1) == [9,121,1]

Теперь у меня гораздо больше уверенности в этом алгоритме.

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

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

f :: (Integer -> Integer) -> [Integer] -> [Integer]
f g = fmap g . filter odd

И затем я могу пройти только Prelude.id и протестировать g в другом месте с помощью модульного тестирования.

4b9b3361

Ответ 1

Как насчет следующих свойств:

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

Кстати, odd легче читать, чем \x -> x % 2 == 1

Ответ 2

Справочный алгоритм

Очень часто существует (возможно, неэффективная) эталонная реализация и проверка на это. На самом деле, это одна из наиболее распространенных стратегий quickcheck при реализации числовых алгоритмов. Но не каждая часть алгоритма нуждается в этом. Иногда есть некоторые свойства, которые полностью характеризуют алгоритм. В этом вопросе есть комментарий Ingo: Эти свойства определяют результаты вашего алгоритма (до порядка и дубликатов). Чтобы восстановить порядок и дубликаты, вы можете изменить свойства, чтобы включить "в результирующий список, усеченный после позиции исходного элемента", и наоборот в другом свойстве.

Гранулярность тестов

Конечно, учитывая композитность Хаскелла, приятно проверить каждую разумную небольшую часть алгоритма. Я доверяю, например. \x -> x*x и filter odd в качестве ссылки, не глядя дважды.

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