In FsCheck, how to generate a test record with non-negative fields?
Asked Answered
H

2

5

In F#, I have a record with a few fields:

    type myRecord = { a:float; b:float; c:float }

I am using FsCheck to test some properties which use this record. For (a contrived) example,

    let verify_this_property (r:myRecord) = myFunction(r) = (r.a * r.b) / r.c

Due to the internal implementation restrictions of myFunction, I would like to have FsCheck create test cases in which each of the fields a,b,c are restricted to non-negative floats.

I suspect this requires creating a generator for myRecord, but I have not been able to find any examples of how to do this.

Can anyone supply guidance?

Hermilahermina answered 16/11, 2011 at 23:53 Comment(2)
Have you carefully read this FsCheck Wiki page? It has an example of how to write and register a generator.Holler
@fmr--Yes I have carefully read all the FsCheck Wiki pages. While the steps for writing a simple generator are provided (not very clearly IMHO), I am unable to determine how to extend this to create a generator for records -- hence, the SO question.Hermilahermina
T
6

Try this:

type Generators = 
    static member arbMyRecord =
        fun (a,b,c) -> { myRecord.a = a; b = b; c = c }
        <!> (Arb.generate<float> |> Gen.suchThat ((<) 0.) |> Gen.three)
        |> Arb.fromGen

Arb.register<Generators>() |> ignore
Check.Quick verify_this_property

The <!> is an infix map, useful for applicative style. This is an equivalent generator:

type Generators = 
    static member arbMyRecord =
        Arb.generate<float> 
        |> Gen.suchThat ((<) 0.) 
        |> Gen.three
        |> Gen.map (fun (a,b,c) -> { myRecord.a = a; b = b; c = c })
        |> Arb.fromGen

If you don't want to globally register your generator, you can use forAll:

Check.Quick (forAll Generators.arbMyRecord verify_this_property)

Shrinking left as an exercise ;)

Troika answered 23/11, 2011 at 4:7 Comment(5)
What's the meaning of the "<!>" construct? I haven't seen that before.Hermilahermina
@DavidH : it's an infix map, useful for applicative style. See bugsquash.blogspot.com/2010/12/…Troika
@DavidH : explained a bit more.Troika
where does forAll live? I can't seem to find itSheila
@Sheila and others that looked for this: forAll lives in the Prop module. Also, It seems that Gen.suchAt is deprecated, and where or filter should be used insteadMohammadmohammed
S
3

You can avoid creating custom generator by using FsCheck conditional properties

let verify_this_property (r:myRecord) =
    (r.a > 0.0 && r.b > 0.0 && r.c > 0.0) ==> lazy (myFunction r = (r.a * r.b) * r.c)

Though this will result in (substantially?) slower execution of the test since FsCheck will have to discard all unsuitable test entries.

Skvorak answered 17/11, 2011 at 11:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.