Как правильно пройти тест FsCheck

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

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

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


person wolf    schedule 22.11.2018    source источник
comment
Опубликуйте полный MCVE. Мы можем видеть свойство, которое очевидно фальсифицируется вводом типа [null]. Генератор по умолчанию для string list даст такие входные данные. Вопрос в том, что вы делаете для создания входных данных и как вы их используете.   -  person scrwtp    schedule 22.11.2018
comment
У вас есть функция, которая генерирует списки? Если вы это сделаете, то эту функцию в сочетании с этой вы должны передать Check.Verbose. Функция list — это функция проверки, поэтому, конечно, она должна поддаваться фальсификации с любыми входными данными, которые заставляют ее возвращать false. Вы используете эту функцию для проверки некоторых других функций.   -  person AMieres    schedule 22.11.2018
comment
Поскольку это домашнее задание, я не дам вам полного ответа, но дам подсказку: List.contains " " проверяет не пустые строки, а строки, содержащие один пробел. Если вы хотите проверить наличие пустых строк, вы должны были написать List.contains "".   -  person rmunn    schedule 22.11.2018
comment
Falsifiable, after 1 test (1 shrink) (StdGen (1956008827,296526183)): Original: [""; "\012"] (At least one control character has been escaped as a char code, e.g. \023) Shrunk: [""] . Значит ли это, что из-за этого мой тест возвращает false? Я заявил, что это должно быть ложно, и если это ложно, разве это не нормально?   -  person wolf    schedule 22.11.2018


Ответы (2)


Я думаю, вы еще не совсем понимаете 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 использует термин (то есть функция, которая должна всегда возвращать значение true, независимо от того, какие входные данные). Чтобы создать свойство в стиле 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 для проверки того, что обе реализации возвращают один и тот же результат для любого заданного ввода.

person rmunn    schedule 22.11.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 вы можете проверить свойства (или правила), которые всегда должны быть истинными. Требуется немного творческого подхода, чтобы придумать хорошие для проверки и само собой разумеющиеся, иногда это непросто.

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

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

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

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

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

Теперь, если вы вызовете FsCheck:

FsCheck.Verbose myFuncConcatenation

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

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.
person AMieres    schedule 22.11.2018