В настоящее время я пишу тест на основе свойств для проверки функции расчета скорости в f # с 4 параметрами с плавающей запятой, и все параметры имеют определенные условия для их правильности (например, a > 0,0 && a ‹ 1,0 и b > a) . У меня есть функция, проверяющая выполнение этих условий и возвращающая логическое значение. У меня вопрос: в моем тестовом коде, использующем [Property>] в FsCheck.Xunit, как мне ограничить генератор для проверки кодов, используя только значения, соответствующие моим конкретным условиям для параметров?
Тестирование на основе свойств в F# с использованием условных параметров
Ответы (2)
Если вы используете FsCheck, вы можете использовать функцию Gen.filter
и функцию Gen.map
.
Допустим, у вас есть функция funToBeTested
, которую вы тестируете, для которой требуется, чтобы a ‹ b:
let funToBeTested a b = if a < b then a + b else failwith "a should be less than b"
И вы проверяете свойство, которое funToBeTested
пропорционально входным данным:
let propertyTested a b = funToBeTested a b / 2. = funToBeTested (a / 2.) (b / 2.)
У вас также есть предикат, который проверяет требования условия для a и b:
let predicate a b = a > 0.0 && a < 1.0 && b > a
Мы начинаем с генерации float
чисел, используя Gen.choose
и Gen.map
, таким образом уже получаются значения только от 0.0 до 1.0:
let genFloatFrom0To1 = Gen.choose (0, 10000) |> Gen.map (fun i -> float i / 10000.0 )
Затем мы генерируем two
чисел с плавающей запятой от 0 до 1 и filter
их с помощью функции predicate
выше.
let genAB = Gen.two genFloatFrom0To1 |> Gen.filter (fun (a,b) -> predicate a b )
Теперь нам нужно создать новый тип TestData
для использования этих значений:
type TestData = TestData of float * float
и мы сопоставляем полученное значение с TestData
let genTest = genAB |> Gen.map TestData
Далее нам нужно зарегистрировать genTest
в качестве генератора для TestData
, для этого мы создадим новый класс со статическим членом типа Arbitrary<TestData>
:
type MyGenerators =
static member TestData : Arbitrary<TestData> = genTest |> Arb.fromGen
Arb.register<MyGenerators>() |> ignore
наконец, мы проверяем свойство, используя TestData
в качестве входных данных:
Check.Quick (fun (TestData(a, b)) -> propertyTested a b )
ОБНОВИТЬ:
Простой способ составить разные генераторы — использовать gen
Computation Expression:
type TestData = {
a : float
b : float
c : float
n : int
}
let genTest = gen {
let! a = genFloatFrom0To1
let! b = genFloatFrom0To1
let! c = genFloatFrom0To1
let! n = Gen.choose(0, 30)
return {
a = a
b = b
c = c
n = n
}
}
type MyGenerator =
static member TestData : Arbitrary<TestData> = genTest |> Arb.fromGen
Arb.register<MyGenerator>() |> ignore
let ``Test rate Calc`` a b c n =
let r = rCalc a b c
(float) r >= 0.0 && (float) r <= 1.0
Check.Quick (fun (testData:TestData) ->
``Test rate Calc``
testData.a
testData.b
testData.c
testData.n)
Test rate Calc
должна получить TestData(a, b, c)
в качестве параметра, в настоящее время она получает a b c
.
- person AMieres; 22.02.2019
TestData
с DU на запись, потому что это становится беспорядочным, когда в DU слишком много полей.
- person AMieres; 23.02.2019
Ответ @AMieres - отличное объяснение всего, что вам нужно, чтобы решить эту проблему!
Одно небольшое дополнение заключается в том, что использование Gen.filter
может быть сложным, если предикат не выполняется для большого количества элементов, которые производит ваш генератор, потому что тогда генератор должен работать в течение длительного времени, пока не найдет достаточное количество допустимых элементов.
В примере @AMieres это нормально, потому что генератор уже генерирует числа в правильном диапазоне, поэтому он только проверяет, что второе больше, что будет иметь место примерно для половины случайно сгенерированных пар.
Если вы можете написать это так, чтобы всегда генерировать допустимые значения, то это немного лучше. Моя версия для этого конкретного случая состояла бы в том, чтобы использовать map
для замены чисел, чтобы меньшее всегда было первым:
let genFloatFrom0To1 = Gen.choose (0, 10000) |> Gen.map (fun i -> float i / 10000.0 )
let genAB = Gen.two genFloatFrom0To1 |> Gen.map (fun (a, b) -> min a b, max a b)