属性 使用条件参数在 F# 中进行基于测试
Property Based Testing in F# using conditional parameters
我目前正在编写一个基于 属性 的测试来测试 f# 中具有 4 个浮点参数的速率计算函数,并且所有参数都有特定的条件才能有效(例如,a > 0.0 && a < 1.0,且 b > a)。我确实有一个函数检查是否满足这些条件并返回一个布尔值。我的问题是,在我使用 [属性>] in FsCheck.Xunit 的测试代码中,如何限制生成器仅使用满足我的参数特定条件的值来测试代码?
如果您正在使用 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
我们首先使用 Gen.choose
和 Gen.map
生成 float
个数字,这种方式已经生成了仅从 0.0 到 1.0 的值:
let genFloatFrom0To1 = Gen.choose (0, 10000) |> Gen.map (fun i -> float i / 10000.0 )
然后我们使用上面的predicate
函数生成two
从0到1的浮点数和filter
它们
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
的生成器,因为我们创建了一个新的 class,其静态成员类型为 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)
@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)
我目前正在编写一个基于 属性 的测试来测试 f# 中具有 4 个浮点参数的速率计算函数,并且所有参数都有特定的条件才能有效(例如,a > 0.0 && a < 1.0,且 b > a)。我确实有一个函数检查是否满足这些条件并返回一个布尔值。我的问题是,在我使用 [属性>] in FsCheck.Xunit 的测试代码中,如何限制生成器仅使用满足我的参数特定条件的值来测试代码?
如果您正在使用 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
我们首先使用 Gen.choose
和 Gen.map
生成 float
个数字,这种方式已经生成了仅从 0.0 到 1.0 的值:
let genFloatFrom0To1 = Gen.choose (0, 10000) |> Gen.map (fun i -> float i / 10000.0 )
然后我们使用上面的predicate
函数生成two
从0到1的浮点数和filter
它们
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
的生成器,因为我们创建了一个新的 class,其静态成员类型为 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)
@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)