Как правильно пройти тест FsCheck - PullRequest
0 голосов
/ 22 ноября 2018
let list p = if List.contains " " p || List.contains null p then false else true

У меня есть такая функция, чтобы проверить, хорошо ли отформатирован список или нет.В списке не должно быть пустой строки и нулей.Я не понимаю, чего мне не хватает, так как Check.Verbose list возвращает фальсифицируемый результат.

Как мне подойти к проблеме?

Ответы [ 2 ]

0 голосов
/ 23 ноября 2018

Если добавить превосходный ответ @ rmunn:

, если вы хотите проверить myFunc (да, я также переименовал вашу list функцию), вы можете сделать это, создав несколько фиксированных случаев, ответы на которые вы уже знаете.например:

let myFunc p = if List.contains " " p || List.contains null p then false else true

let tests =
    testList "myFunc" [
        testCase "empty list"    <| fun()-> "empty" |> Expect.isTrue  (myFunc [      ])
        testCase "nonempty list" <| fun()-> "hi"    |> Expect.isTrue  (myFunc [ "hi" ])
        testCase "null case"     <| fun()-> "null"  |> Expect.isFalse (myFunc [ null ])
        testCase "empty string"  <| fun()-> "\"\""  |> Expect.isFalse (myFunc [ ""   ])
    ]

Tests.runTests config tests

Здесь я использую библиотеку тестирования с именем Expecto.

Если вы запустите это, вы увидите, что один из тестов не пройден:

Ошибка!myFunc / пустая строка: "".Фактическое значение было истинным, но ожидалось, что оно будет ложным.

, поскольку в вашей исходной функции есть ошибка;он проверяет пробел " " вместо пустой строки "".

После исправления все тесты проходят:

4 теста выполняются в 00: 00: 00.0105346 для myFunc -4 пройдено, 0 проигнорировано, 0 не выполнено, 0 с ошибкой.Успех!

На данный момент вы проверили только 4 простых и очевидных случая с нулем или одним элементом в каждом.Много раз функции терпят неудачу при подаче более сложных данных.Проблема в том, сколько еще тестовых примеров вы можете добавить?Возможности буквально безграничны!

FsCheck

Именно здесь FsCheck может вам помочь.С помощью FsCheck вы можете проверить свойства (или правила), которые всегда должны быть истинными.Требуется немного творческого подхода, чтобы подумать о хороших, чтобы проверить и получить, а иногда это нелегко.

В вашем случае мы можем проверить на конкатенацию.Правило будет выглядеть следующим образом:

  • Если два списка объединены, то результат MyFunc, примененный к объединению, должен быть true, если оба списка правильно сформированы, и false, если любой из нихискажен.

Вы можете выразить это как функцию следующим образом:

let myFuncConcatenation l1 l2 = myFunc (l1 @ l2) = (myFunc l1 && myFunc l2)

l1 @ l2 - это объединение обоих списков.

Теперь, если выcall FsCheck:

FsCheck.Verbose myFuncConcatenation

Он пробует 100 различных комбинаций, пытаясь заставить его потерпеть неудачу, но в конце он дает вам Ok:

0:
["X"]
["^"; ""]
1:
["C"; ""; "M"]
[]
2:
[""; ""; ""]
[""; null; ""; ""]
3:
...
Ok, passed 100 tests.

Это не обязательно означает, что ваша функцияправильно, все еще может быть неудачная комбинация, которую FsCheck не пробовал, или это может быть неправильно по-другому.Но это довольно хороший признак того, что это правильно с точки зрения свойства конкатенации.

Тестирование свойства конкатенации с помощью FsCheck фактически позволило нам вызвать myFunc 300 раз с различными значениями и доказать, что оно не аварийно завершилось или вернуло неожиданное значение.

FsCheck выполняетНе заменяйте индивидуальное тестирование, оно дополняет его:

Обратите внимание, что если вы запустили FsCheck.Verbose myFuncConcatenation поверх исходной функции, в которой была ошибка, она все равно бы прошла.Причина в том, что ошибка не зависит от свойства конкатенации.Это означает, что у вас всегда должно быть индивидуальное тестирование, когда вы проверяете наиболее важные случаи, и вы можете дополнить его FsCheck для проверки других ситуаций.

Вот другие свойства, которые вы можете проверить, они проверяют два ложныхусловия независимо:

let myFuncHasNulls l = if List.contains null l then myFunc l = false else true
let myFuncHasEmpty l = if List.contains ""   l then myFunc l = false else true

Check.Quick myFuncHasNulls
Check.Quick myFuncHasEmpty

// Ok, passed 100 tests.
// Ok, passed 100 tests.
0 голосов
/ 22 ноября 2018

Я думаю, вы еще не совсем поняли FsCheck.Когда вы делаете Check.Verbose someFunction, FsCheck генерирует несколько случайных входных данных для вашей функции и завершается ошибкой, если функция когда-либо возвращает false .Идея состоит в том, что функция, которую вы передаете в Check.Verbose, должна быть свойством, которое будет всегда истинным независимо от того, что ввод .Например, если вы переверните список дважды, он должен вернуть исходный список независимо от того, каким был первоначальный список.Это свойство обычно выражается следующим образом:

let revTwiceIsSameList (lst : int list) =
    List.rev (List.rev lst) = lst

Check.Verbose revTwiceIsSameList  // This will pass

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

let verifyMyFunc (input : string list) =
    if (input is well-formed) then  // TODO: Figure out how to check that
        myFunc input = true
    else
        myFunc input = false

Check.Verbose verifyMyFunc

(Обратите внимание, что я назвал вашу функцию myFunc вместо list, потому что в целомКак правило, вы никогда не должны называть функцию list. Имя list - это тип данных (например, string list или int list), и если вы назоветефункция list, вы просто запутаете себя позже, когда одно и то же имя будет иметь два разных значения.)

Теперь проблема в следующем: как вы пишете «входные данные правильно сформированы» частьмой verifyMyFunc пример?Вы не можете просто использовать свою функцию, чтобы проверить ее, потому что это будет проверять вашу функцию на себя, что не является полезным тестом.(Тест по сути стал бы «myFunc input = myFunc input», который всегда возвращал бы true, даже если в вашей функции была ошибка - если, конечно, ваша функция не возвращала случайный ввод).Поэтому вам нужно написать другую функцию, чтобы проверить правильность ввода, и здесь проблема в том, что написанная вами функция является лучшим и наиболее правильным способом проверки правильности ввода.Если бы вы написали другую функцию для проверки, она в конечном итоге сводилась бы к not (List.contains "" || List.contains null), и снова вы, по сути, проверяли бы свою функцию по отношению к самой себе.

В этом конкретном случае я не думаю, чтоFsCheck - правильный инструмент для работы, потому что ваша функция очень проста.Это домашнее задание, где ваш инструктор требует от вас использовать FsCheck?Или вы пытаетесь выучить FsCheck самостоятельно и используете это упражнение, чтобы научить себя FsCheck?Если это первое, то я бы предложил указать вашему инструктору на этот вопрос и посмотреть, что он скажет о моем ответе.Если это последнее, то я бы предложил найти несколько более сложную функцию для изучения FsCheck.В этом случае полезной будет функция, в которой вы можете найти свойство, которое всегда должно быть истинным, как в примере List.rev (при двойном обращении к списку следует восстановить исходный список, так что это полезное свойство для тестирования).Или, если у вас возникают проблемы с поиском всегда истинного свойства, по крайней мере, найдите функцию, которую вы можете реализовать, по крайней мере, двумя различными способами, чтобы вы могли использовать FsCheck, чтобы проверить, что обе реализации возвращают один и тот же результат для любого заданного ввода.

...