unit-testingf#xunitfscheckproperty-based-testing

Property Based Testing in F# using conditional parameters


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?


Solution

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