Сложность мышления свойств для 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 ответа

Уже есть много конкретных ответов, поэтому я постараюсь дать некоторые общие ответы, которые могут дать вам некоторые идеи.

  1. Индуктивные свойства для рекурсивных функций. Для простых функций это, вероятно, означает повторную реализацию рекурсии. Тем не менее, оставайтесь простыми: в то время как реальная реализация чаще всего развивается (например, она становится рекурсивной, вы добавляете памятки,...), сохраняете свойство простым. Комбинатор свойств ==> здесь обычно пригодится. Ваша функция пар может быть хорошим примером.
  2. Свойства, которые содержат несколько функций в модуле или типе. Обычно это происходит при проверке абстрактных типов данных. Например: добавление элемента в массив означает, что массив содержит этот элемент. Это проверяет согласованность Array.add и Array.contains.
  3. Круглые обходы: это хорошо для преобразований (например, синтаксический анализ, сериализация) - создать произвольное представление, сериализовать его, десериализовать его, проверить, что оно равно оригиналу. Вы можете сделать это с помощью splitOn и concat.
  4. Общие свойства как проверки работоспособности. Ищите общеизвестные свойства, которые могут сохраняться - такие как коммутативность, ассоциативность, идемпотентность (применение чего-либо дважды не меняет результат), рефлексивность и т. Д. Идея здесь состоит в том, чтобы немного поработать над функцией - посмотрите, действительно ли она делает что-то странное,

Как общий совет, постарайтесь не делать из этого слишком большой сделки. Для 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) ". К сожалению, логика здесь, вероятно, будет сопоставима по сложности с самой реализацией.
Другие вопросы по тегам