Я хотел бы знать, для чего нужно тестирование свойств, что это за сладкая точка, где она должна использоваться. Пусть у меня есть пример функции, которую я хочу проверить:
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
в другом месте с помощью модульного тестирования.