FsCheck


Model-based Testing

FsCheck also allows you to test objects, which usually encapsulate internal state through a set of methods. FsCheck, through small extension, allows you to do model-based specification of a class under test. Consider the following class, with an artificial bug in it:

type Counter() =
  let mutable n = 0
  member __.Inc() = n <- n + 1
  member __.Dec() = if n > 2 then n <- n - 2 else n <- n - 1
  member __.Get = n
  member __.Reset() = n <- 0
  override __.ToString() = sprintf "Counter=%i" n

We'll elide the class definition in C#, it's very similar.

As a model to test this class we can use an int value which is an abstraction of the object's internal state. The idea is that each operation on the class (in this case, Inc and Dec) affects both the model and the actual object, and after each such operation, the model and the actual instance should still be equivalent.

With this idea in mind, you can write a specification of the Counter class using an int model as follows (full example in C# below):

let spec =
  let inc = { new Command<Counter, int>() with
                    override __.RunActual counter = counter.Inc(); counter
                    override __.RunModel m = m + 1
                    override __.Post(counter, m) = counter.Get = m |@ sprintf "model: %i <> %A" m counter
                    override __.ToString() = "inc" }
                           
  let dec = { new Command<Counter, int>() with
                    override __.RunActual counter = counter.Dec(); counter
                    override __.RunModel m = m - 1
                    override __.Post(counter, m) = counter.Get = m |@ sprintf "model: %i <> %A" m counter
                    override __.ToString() = "dec" }
  
  { new ICommandGenerator<Counter,int> with
      member __.InitialActual = Counter()
      member __.InitialModel = 0
      member __.Next model = Gen.elements [inc;dec] }

A specification is put together for FsCheck as an object that implementents ICommandGenerator<'typeUnderTest,'modelType>. It should return an initial object and an initial model of that object; and it should return a generator of Command objects.

Each Command typically represents one method to call on the object under test, and describes what happens to the model and the object when the command is executed. Also, it can assert preconditions that need to hold before executing the command: FsCheck will not execute that command if the precondition does not hold. It asserts postconditions that should hold after a command is executed: FsCheck fails the test if a postcondition does not hold.

Preferably also override ToString in each command so that counterexamples can be printed.

A specification can be checked as follows:

Check.Quick (Command.toProperty spec)
Falsifiable, after 4 tests (1 shrink) (StdGen (938340802, 297043896)):
Label of failing property: model: 2 <> Counter=1
Original:
[inc; inc; inc; inc; dec]
Shrunk:
[inc; inc; inc; dec]

Notice that not only has FsCheck found our 'bug', it has also produced the minimal sequence that leads to it.

Finally, in C#, all this looks as follows:

public class CounterSpec : ICommandGenerator<Counter, int> {

    public Gen<Command<Counter, int>> Next(int value) {
        return Gen.Elements(new Command<Counter, int>[] { new Inc(), new Dec() });
    }

    public Counter InitialActual => new Counter();

    public int InitialModel => 0;

    private class Inc : Command<Counter,int> {
        public override Counter RunActual(Counter c) {
            c.Inc();
            return c;
        }

        public override int RunModel(int m) => m + 1;

        public override Property Post(Counter c, int m) {
            return (m == c.Get()).ToProperty();
        }

        public override string ToString() => "inc";
    }

    private class Dec : Command<Counter,int>{
        public override Counter RunActual(Counter c) {
            c.Dec();
            return c;
        }

        public override int RunModel(int m) => m - 1;

        public override Property Post(Counter c, int m) {
            return (m == c.Get()).ToProperty();
        }

        public override string ToString() => "dec";
    }
}

And to run:

new CounterSpec()
    .ToProperty()
    .QuickCheck();
namespace FsCheck
namespace System
Multiple items
type Counter =
  new : unit -> Counter
  member Dec : unit -> unit
  member Inc : unit -> unit
  member Reset : unit -> unit
  override ToString : unit -> string
  member Get : int

--------------------
new : unit -> Counter
val mutable n : int
val __ : Counter
val sprintf : format:Printf.StringFormat<'T> -> 'T
val spec : ICommandGenerator<Counter,int>
val inc : Command<Counter,int>
Multiple items
module Command

from FsCheck

--------------------
type Command<'Actual,'Model> =
  new : unit -> Command<'Actual,'Model>
  abstract member Post : 'Actual * 'Model -> Property + 1 overload
  abstract member Pre : 'Model -> bool + 1 overload
  abstract member RunActual : 'Actual -> 'Actual
  abstract member RunModel : 'Model -> 'Model

--------------------
new : unit -> Command<'Actual,'Model>
Multiple items
val int : value:'T -> int (requires member op_Explicit)

--------------------
[<Struct>]
type int = int32

--------------------
type int<'Measure> =
  int
val counter : Counter
member Counter.Inc : unit -> unit
val __ : Command<Counter,int>
abstract member Command.RunModel : 'Model -> 'Model
val m : int
override Command.Post : 'Actual * 'Model -> Property
property Counter.Get: int with get
Object.ToString() : string
val dec : Command<Counter,int>
member Counter.Dec : unit -> unit
type ICommandGenerator<'Actual,'Model> =
  abstract member Next : 'Model -> Gen<Command<'Actual,'Model>>
  abstract member InitialActual : 'Actual
  abstract member InitialModel : 'Model
val __ : ICommandGenerator<Counter,int>
property ICommandGenerator.InitialModel: int with get
abstract member ICommandGenerator.Next : 'Model -> Gen<Command<'Actual,'Model>>
val model : int
Multiple items
module Gen

from FsCheck

--------------------
type Gen<'a> =
  private | Gen of (int -> StdGen -> 'a)
    interface IGen
    member private Map : f:('a -> 'b) -> Gen<'b>
    static member ( <!> ) : f:('a1 -> 'a2) * a:Gen<'a1> -> Gen<'a2>
    static member ( <*> ) : f:Gen<('a1 -> 'a2)> * a:Gen<'a1> -> Gen<'a2>
    static member ( >>= ) : m:Gen<'a1> * k:('a1 -> Gen<'a2>) -> Gen<'a2>
val elements : xs:seq<'b> -> Gen<'b>
type Check =
  static member All : config:Config * test:Type -> unit + 1 overload
  static member Method : config:Config * methodInfo:MethodInfo * ?target:obj -> unit
  static member One : config:Config * property:'Testable -> unit + 1 overload
  static member Quick : property:'Testable -> unit + 1 overload
  static member QuickAll : test:Type -> unit + 1 overload
  static member QuickThrowOnFailure : property:'Testable -> unit
  static member QuickThrowOnFailureAll : test:Type -> unit + 1 overload
  static member Verbose : property:'Testable -> unit + 1 overload
  static member VerboseAll : test:Type -> unit + 1 overload
  static member VerboseThrowOnFailure : property:'Testable -> unit
  ...
static member Check.Quick : property:'Testable -> unit
static member Check.Quick : name:string * property:'Testable -> unit
val toProperty : spec:ICommandGenerator<'Actual,'Model> -> Property