This post explains how to configure FsCheck to create arbitrary Version values.

When I unit test generic classes or methods, I often like to use Version as one of the type arguments. The Version class is a great test type because

  • it's readily available, as it's defined in the System namespace in mscorlib
  • it overrides Equals so that it's easy to compare two values
  • it's a complex class, because it composes four integers, so it's a good complement to String, Int32, Object, Guid, and other primitive types

Recently, I've been picking up FsCheck to do Property-Based Testing, but out of the box it doesn't know how to create arbitrary Version instances.

It turns out that you can easily and elegantly tell FsCheck how to create arbitrary Version instances, but since I haven't seen it documented, I thought I'd share my solution:

type Generators =
    static member Version() =
        Arb.generate<byte>
        |> Gen.map int
        |> Gen.four
        |> Gen.map (fun (ma, mi, bu, re) -> Version(ma, mi, bu, re))
        |> Arb.fromGen

As the FsCheck documentation explains, you can create custom Generator by defining a static class that exposes members that return Arbitrary<'a> - in this case Arbitrary<Version>.

If you'd like me to walk you through what happens here, read on, and I'll break it down for you.

First, Arb.generate<byte> is a Generator of Byte values. While FsCheck doesn't know how to create arbitrary Version values, it does know how to create arbitrary values of various primitive types, such as Byte, Int32, String, and so on. The Version constructors expect components as Int32 values, so why did I select Byte values instead? Because Version doesn't accept negative numbers, and if I had kicked off my Generator with Arb.generate<int>, it would have created all sorts of integers, including negative values. While it's possible to filter or modify the Generator, I thought it was easier to simply kick off the Generator with Byte values, because they are never negative.

Second, Gen.map int converts the initial Gen<byte> to Gen<int> by invoking F#'s built-in int conversion function.

Third, Gen.four is a built-in FsCheck Generator Combinator that converts a Generator into a Generator of four-element tuples; in this case it converts Get<int> to Gen<int * int * int * int>: a Generator of a four-integer tuple.

Fourth, Gen.map (fun (ma, mi, bu, re) -> Version(ma, mi, bu, re)) converts Gen<int * int * int * int> to Gen<Version> by another application of Gen.map. The function supplied to Gen.map takes the four-element tuple of integers and invokes the Version constructor with the major, minor, build, and revision integer values.

Finally, Arb.fromGen converts Gen<Version> to Arbitrary<Version>, which is what the member must return.

To register the Generators custom class with FsCheck, I'm currently doing this:

do Arb.register<Generators>() |> ignore

You can see this entire code in context here.



Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or somewhere else with a permalink. Ping me with the link, and I may respond.

Published

Tuesday, 11 March 2014 10:01:00 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!
Published: Tuesday, 11 March 2014 10:01:00 UTC