Тестирование на основе свойств в F # с использованием условных параметров - PullRequest
0 голосов
/ 21 февраля 2019

В настоящее время я пишу тест на основе свойств, чтобы протестировать функцию вычисления ставки в f # с 4 параметрами с плавающей запятой, и у всех параметров есть определенные условия, чтобы они были действительными (например, a> 0.0 && a <1.0 и b> а).У меня есть функция, проверяющая, выполняются ли эти условия, и возвращающая логическое значение.Мой вопрос заключается в том, как в моем тестовом коде, использующем [Property>] в FsCheck.Xunit, как ограничить генератор для тестирования кодов, используя только значения, соответствующие моим конкретным условиям для параметров?

Ответы [ 2 ]

0 голосов
/ 22 февраля 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)
0 голосов
/ 21 февраля 2019

Если вы используете FsCheck, то вы можете использовать функцию Gen.filter и функцию Gen.map.

Допустим, у вас есть проверяемая функция funToBeTested, которая требует, чтобы 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 Выражения вычислений:

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)         
...