Трудности с поиском свойств для FsCheck
-
20-09-2019 - |
Вопрос
Мне удалось получить xUnit ( Единица измерения ) работаю над своим маленьким образцом сборки.Теперь я хочу посмотреть, смогу ли я грокнуть Проверка FsCheck слишком.Моя проблема в том, что я в тупике, когда дело доходит до определения тестовых свойств для моих функций.
Может быть, у меня просто нет хорошего примерного набора функций, но какие были бы хорошие тестовые свойства для этих функций, например?
//transforms [1;2;3;4] into [(1,2);(3,4)]
pairs : 'a list -> ('a * 'a) list //'
//splits list into list of lists when predicate returns
// true for adjacent elements
splitOn : ('a -> 'a -> bool) -> 'a list -> 'a list list
//returns true if snd is bigger
sndBigger : ('a * 'a) -> bool (requires comparison)
Нет правильного решения
Другие советы
Уже есть много конкретных ответов, поэтому я попытаюсь дать несколько общих ответов, которые могут натолкнуть вас на некоторые идеи.
- Индуктивные свойства для рекурсивных функций.Для простых функций это, вероятно, равносильно повторной реализации рекурсии.Тем не менее, сделайте это простым:в то время как фактическая реализация чаще всего эволюционирует (напримерэто становится хвостовой рекурсией, вы добавляете запоминание, ...) сохраняйте свойство простым.Здесь обычно пригодится комбинатор свойств ==> .Ваша функция pairs может стать хорошим примером.
- Свойства, которые относятся к нескольким функциям в модуле или типе.Обычно это имеет место при проверке абстрактных типов данных.Например:добавление элемента в массив означает, что массив содержит этот элемент.Это проверяет согласованность Array.add и Array.contains.
- Поездки туда и обратно:это хорошо для конверсий (напримерparsing, сериализация) - генерирует произвольное представление, сериализует его, десериализует, проверяет, что оно равно исходному.Возможно, вы сможете сделать это с помощью splitOn и concat.
- Общие свойства как проверка на вменяемость.Ищите общеизвестные свойства, которые могут сохраняться - такие вещи, как коммутативность, ассоциативность, идемпотентность (применение чего-либо дважды не меняет результата), рефлексивность и т.д.Идея здесь скорее в том, чтобы немного потренировать функцию - посмотреть, не делает ли она чего-нибудь действительно странного.
В качестве общего совета постарайтесь не придавать этому слишком большого значения.Для sndBigger хорошим свойством было бы:
пусть `должно возвращать true тогда и только тогда, когда snd больше` (a: int) (b: int) = sndBigger (a,b) = b > a
И это, вероятно, именно та реализация.Не беспокойтесь об этом - иногда простой, старомодный модульный тест - это как раз то, что вам нужно.Не нужно испытывать чувство вины!:)
Может быть эта ссылка (командой Pex) также высказывает некоторые идеи.
Я начну с sndBigger
- это очень простая функция, но вы можете написать некоторые свойства, которые должны относиться к ней.Например, что происходит, когда вы меняете местами значения в кортеже:
// Reversing values of the tuple negates the result
let swap (a, b) = (b, a)
let prop_sndBiggerSwap x =
sndBigger x = not (sndBigger (swap x))
// If two elements of the tuple are same, it should give 'false'
let prop_sndBiggerEq a =
sndBigger (a, a) = false
Редактировать: Это правило prop_sndBiggerSwap
не всегда выполняется (см. Комментарий от квб).Однако следующее должно быть правильным:
// Reversing values of the tuple negates the result
let prop_sndBiggerSwap a b =
if a <> b then
let x = (a, b)
sndBigger x = not (sndBigger (swap x))
Что касается pairs
функция, квб уже опубликовал несколько хороших идей.Кроме того, вы могли бы проверить, что превращение преобразованного списка обратно в список элементов возвращает исходный список (вам нужно будет обработать случай, когда входной список нечетный - в зависимости от того, какой pairs
функция должна работать в этом случае):
let prop_pairsEq (x:_ list) =
if (x.Length%2 = 0) then
x |> pairs |> List.collect (fun (a, b) -> [a; b]) = x
else true
Для splitOn
, мы можем протестировать аналогичную вещь - если вы объедините все возвращенные списки, это должно привести к получению исходного списка (это не проверяет поведение разделения, но с этого стоит начать - это, по крайней мере, гарантирует, что никакие элементы не будут потеряны).
let prop_splitOnEq f x =
x |> splitOn f |> List.concat = x
Я не уверен, что Проверка FsCheck может справиться с этим, хотя (!) потому что свойство принимает функцию в качестве аргумента (поэтому ему нужно было бы генерировать "случайные функции").Если это не сработает, вам нужно будет предоставить пару более специфических свойств с помощью какой-нибудь рукописной функции f
.Далее, реализуем проверку того, что f
возвращает true для всех смежных пар в разделенных списках (как квб предполагает) на самом деле не так уж и сложно:
let prop_splitOnAdjacentTrue f x =
x |> splitOn f
|> List.forall (fun l ->
l |> Seq.pairwise
|> Seq.forall (fun (a, b) -> f a b))
Вероятно, единственное последнее, что вы могли бы проверить, это то, что f
ВОЗВРАТ false
когда вы даете ему последний элемент из одного списка и первый элемент из следующего списка.Следующее не является полностью полным, но оно показывает, как действовать:
let prop_splitOnOtherFalse f x =
x |> splitOn f
|> Seq.pairwise
|> Seq.forall (fun (a, b) -> lastElement a = firstElement b)
Последний пример также показывает, что вам следует проверить, является ли splitOn
функция может возвращать пустой список как часть возвращаемого списка результатов (потому что в этом случае вы не смогли найти первый / последний элемент).
Для некоторого кода (например sndBigger
), реализация настолько проста, что любое свойство будет, по крайней мере, таким же сложным, как исходный код, поэтому тестирование с помощью FsCheck может не иметь смысла.Однако для двух других функций вот некоторые вещи, которые вы могли бы проверить:
pairs
- Что ожидается, когда исходная длина не делится на два?Вы могли бы проверить, не генерируется ли исключение, если это правильное поведение.
List.map fst (pairs x) = evenEntries x
иList.map snd (pairs x) = oddEntries x
для простых функцийevenEntries
иoddEntries
который вы можете написать.
splitOn
- Если я понимаю ваше описание того, как должна работать функция, то вы могли бы проверить условия типа "Для каждого списка в результате
splitOn f l
, никакие две последовательные записи не удовлетворяют f" и "Спискам взятия(l1,l2)
ОтsplitOn f l
попарно,f (last l1) (first l2)
удерживает".К сожалению, логика здесь, вероятно, будет сравнима по сложности с самой реализацией.
- Если я понимаю ваше описание того, как должна работать функция, то вы могли бы проверить условия типа "Для каждого списка в результате