Когда вы используете предварительное условие (например, оператор ==>
в FsCheck), вы, по сути, выбрасываете данные. Даже если это произойдет только в одном из ста случаев, вы все равно выбросите 1 входной набор для обычного свойства (поскольку количество выполнений по умолчанию равно 100 в FsCheck).
Выбросить один из 100, наверное, не проблема.
Однако иногда вы выбрасываете намного больше данных. Если, например, вам нужны только положительные числа, вы можете написать предварительное условие, такое как x > 0
, но, поскольку FsCheck генерирует и отрицательные числа, вы выбросите 50 % всех значений после того, как они были сгенерированы. Скорее всего, ваши тесты будут работать медленнее (но, как всегда, когда дело доходит до соображений производительности: измерьте).
Именно по этой причине FsCheck поставляется со встроенными генераторами положительных чисел, но иногда вам требуется более точный контроль диапазона возможных входных значений, как в этот пример.
Если вы делаете ката FizzBuzz, например, вы можете напишите свой тест для случая FizzBuzz следующим образом:
[<Property(MaxFail = 2000)>]
let ``FizzBuzz.transform returns FizzBuzz`` (number : int) =
number % 15 = 0 ==> lazy
let actual = FizzBuzz.transform number
let expected = "FizzBuzz"
expected = actual
Обратите внимание на использование свойства MaxFail
. Причина, по которой вам это нужно, заключается в том, что это предварительное условие отбрасывает 14 из 15 сгенерированных кандидатов. По умолчанию FsCheck попытается проверить 1000 кандидатов, прежде чем сдастся, но если вы отбросите 14 из 15 кандидатов, в среднем у вас будет только 67 значений, соответствующих предварительному условию. Поскольку целью FsCheck по умолчанию является выполнение свойства 100 раз, он сдается.
Как следует из свойства MaxFail
, вы можете настроить значения по умолчанию. С 2000 кандидатов вы должны ожидать в среднем 133 совпадения предварительных условий.
Однако он не кажется особенно эффективным, поэтому вы можете в качестве альтернативы использовать собственный генератор:
[<Property(QuietOnSuccess = true)>]
let ``FizzBuzz.transform returns FizzBuzz`` () =
let fiveAndThrees =
Arb.generate<int> |> Gen.map ((*) (3 * 5)) |> Arb.fromGen
Prop.forAll fiveAndThrees <| fun number ->
let actual = FizzBuzz.transform number
let expected = "FizzBuzz"
expected = actual
При этом используется специальный встроенный файл Arbitrary. Это более эффективно, потому что никакие данные не выбрасываются.
Я склонен использовать предварительные условия, если они выдают только исключить случайный несовпадающий ввод. В большинстве случаев я предпочитаю пользовательские генераторы.
person
Mark Seemann
schedule
24.03.2016