2010-03-15 6 views
13

Мне удалось получить xUnit, работая над моей небольшой сборкой образцов. Теперь я хочу посмотреть, могу ли я тоже заманить FsCheck. Моя проблема в том, что я в тупике, когда дело доходит до определения свойств теста для моих функций.Сложность мышления о свойствах для 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) 

ответ

7

Для некоторого кода (например, sndBigger), реализация настолько проста, что любое имущество, будет по крайней мере, сложным, как исходный код, так что тестирование с помощью FsCheck не может иметь смысл. Однако, для двух других функций, вот некоторые вещи, которые вы можете проверить:

  • pairs
    • Что ожидать, когда исходная длина не делится на два? Вы можете проверить, выбрали ли исключение, если это правильное поведение.
    • List.map fst (pairs x) = evenEntries x и List.map snd (pairs x) = oddEntries x для простых функций evenEntries и oddEntries которые вы можете написать.
  • splitOn
    • Если я понимаю ваше описание того, как предполагается, функция для работы, то вы можете проверить условия, такие как «Для каждого списка в результате splitOn f l, нет двух последовательных записей не удовлетворяют п» и «Принимая списки (l1,l2) от splitOn f l попарно, f (last l1) (first l2) проводит». К сожалению, логика здесь, вероятно, будет сопоставима по сложности с самой реализацией.
+0

Спасибо за советы. Я уже проверяю исключение с xUnit, поэтому нет добавленной стоимости (насколько я могу судить?).На данный момент все это кажется очень сообразительным для меня, и, как вы говорите, в этих конкретных случаях тесты могут быть более сложными, чем оригиналы. – Benjol

+3

Проверки не должны быть более сложными - это действительно поражает цель тестирования. но они могут быть одинаково сложными, по моему опыту. Со временем ваша реализация может стать более сложной (например, оптимизация), в то время как свойства/спецификации обычно остаются более или менее одинаковыми. Поэтому, хотя это может и не иметь смысла, вы, вероятно, будете довольны свойствами позже. –

10

Начну с 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 

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

+0

Спасибо, мне придется попробовать некоторые из этих идей. – Benjol

+0

Отличный, тщательный ответ. Однако ваш 'prop_sndBiggerSwap' не всегда выполняется, так как' sndBigger (z, z) = sndBigger (swap (z, z)) 'для всех' z'. – kvb

+0

@ kvb: Спасибо, ты, конечно, прав! Я редактировал сообщение, чтобы показать исправленную версию правила. –

14

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

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

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

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

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

Возможно, this link (от команды Pex) также дает некоторые идеи.

+0

Спасибо за советы. – Benjol

+0

Данная ссылка мертва. Исправленная ссылка может быть http://research.microsoft.com/en-us/projects/pex/patterns.pdf – PetPaulsen

+0

Спасибо - обновлена ​​ссылка. –

Смежные вопросы