Сложность мышления свойств для FsCheck
Мне удалось заставить 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)
3 ответа
Уже есть много конкретных ответов, поэтому я постараюсь дать некоторые общие ответы, которые могут дать вам некоторые идеи.
- Индуктивные свойства для рекурсивных функций. Для простых функций это, вероятно, означает повторную реализацию рекурсии. Тем не менее, оставайтесь простыми: в то время как реальная реализация чаще всего развивается (например, она становится рекурсивной, вы добавляете памятки,...), сохраняете свойство простым. Комбинатор свойств ==> здесь обычно пригодится. Ваша функция пар может быть хорошим примером.
- Свойства, которые содержат несколько функций в модуле или типе. Обычно это происходит при проверке абстрактных типов данных. Например: добавление элемента в массив означает, что массив содержит этот элемент. Это проверяет согласованность Array.add и Array.contains.
- Круглые обходы: это хорошо для преобразований (например, синтаксический анализ, сериализация) - создать произвольное представление, сериализовать его, десериализовать его, проверить, что оно равно оригиналу. Вы можете сделать это с помощью splitOn и concat.
- Общие свойства как проверки работоспособности. Ищите общеизвестные свойства, которые могут сохраняться - такие как коммутативность, ассоциативность, идемпотентность (применение чего-либо дважды не меняет результат), рефлексивность и т. Д. Идея здесь состоит в том, чтобы немного поработать над функцией - посмотрите, действительно ли она делает что-то странное,
Как общий совет, постарайтесь не делать из этого слишком большой сделки. Для sndBigger хорошим свойством будет:
let `` должен возвращать 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
не всегда держится (см. комментарий от kvb). Однако следующее должно быть правильным:
// 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
Функция, KVB уже опубликовал несколько хороших идей. Кроме того, вы можете проверить, что превращение преобразованного списка обратно в список элементов возвращает исходный список (вам нужно будет обработать случай, когда входной список нечетный - в зависимости от того, что 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 для всех соседних пар в разделенных списках (как предполагает kvb) на самом деле не так уж сложно:
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)
". К сожалению, логика здесь, вероятно, будет сопоставима по сложности с самой реализацией.
- Если я понимаю ваше описание того, как функция должна работать, вы можете проверить условия типа "Для каждого списка в результате