I am currently writing a property based test to test a rate calculation function in f# with 4 float parameters, and all the parameters have specific conditions for them to be valid (for example, a > 0.0 && a < 1.0, and b > a). I do have a function checking if these conditions are met and returning a bool. My question is, in my test code using [Property>] in FsCheck.Xunit, how do I limit the generator to test the codes using only values meeting my specific conditions for the parameters?
If you are using FsCheck then you can use the Gen.filter
function and the Gen.map
function.
Lets say you have this function funToBeTested
that you are testing, that requires that a < b:
let funToBeTested a b = if a < b then a + b else failwith "a should be less than b"
And you are testing the property that funToBeTested
be proportional to the inputs:
let propertyTested a b = funToBeTested a b / 2. = funToBeTested (a / 2.) (b / 2.)
You also have a predicate that checks the condition requirements for a & b:
let predicate a b = a > 0.0 && a < 1.0 && b > a
We start by generating float
numbers using Gen.choose
and Gen.map
, this way already produces values only from 0.0 to 1.0:
let genFloatFrom0To1 = Gen.choose (0, 10000) |> Gen.map (fun i -> float i / 10000.0 )
Then we generate two
floats from 0 to 1 and filter
them using the predicate
function above
let genAB = Gen.two genFloatFrom0To1 |> Gen.filter (fun (a,b) -> predicate a b )
Now we need to create a new type TestData
for using those values:
type TestData = TestData of float * float
and we map the resulting value to TestData
let genTest = genAB |> Gen.map TestData
Next we need to register genTest
as the generator for TestData
for that we create a new class with a static member of type Arbitrary<TestData>
:
type MyGenerators =
static member TestData : Arbitrary<TestData> = genTest |> Arb.fromGen
Arb.register<MyGenerators>() |> ignore
finally we test the property using TestData
as the input:
Check.Quick (fun (TestData(a, b)) -> propertyTested a b )
UPDATE:
An easy way to compose different generators is using 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)