Сложность мышления свойств для FsCheck - PullRequest
13 голосов
/ 15 марта 2010

Мне удалось заставить 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 ]

14 голосов
/ 16 марта 2010

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

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

Как общий совет, старайтесь не делать из этого слишком большой сделки. Для sndBigger хорошим свойством будет:

let `` должен возвращать true тогда и только тогда, когда snd больше`` (a: int) (b: int) = sndBigger (a, b) = b> a

И это, вероятно, именно реализация. Не беспокойтесь об этом - иногда вам нужен простой старомодный модульный тест. Нет необходимости вины! :)

Может быть эта ссылка (от команды Pex) также дает некоторые идеи.

10 голосов
/ 15 марта 2010

Я начну с 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 возвращать пустой список как часть возвращенного списка результатов (потому что в этом случае вы не можете найти первый / последний элемент).

7 голосов
/ 15 марта 2010

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