2

我目前正在编写一个基于属性的测试,以使用 4 个浮点参数在 f# 中测试速率计算函数,并且所有参数都有特定条件才能使其有效(例如,a > 0.0 && a < 1.0 和 b > a) . 我确实有一个函数检查是否满足这些条件并返回一个布尔值。我的问题是,在我使用 FsCheck.Xunit 中的 [Property>] 的测试代码中,如何限制生成器仅使用满足我的参数特定条件的值来测试代码?

4

2 回答 2

3

如果您使用的是 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

我们首先使用and生成float数字,这种方式已经生成了仅从 0.0 到 1.0 的值:Gen.chooseGen.map

let genFloatFrom0To1 = Gen.choose (0, 10000) |> Gen.map (fun i -> float i / 10000.0 )

然后我们使用上面的函数生成two从 0 到 1 的浮点数filterpredicate

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)         
于 2019-02-21T20:59:59.810 回答
3

@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)
于 2019-02-21T23:13:03.240 回答