Property-Based Testing

When a property fails, FsCheck shrinks the input to a minimal counterexample. Also see Hedgehog (F# port with integrated shrinking).

#r "nuget: FsCheck"
open FsCheck

// Properties hold for ALL generated inputs
let revRevIsOriginal (xs: int list) = List.rev (List.rev xs) = xs
Check.Quick revRevIsOriginal                          // Ok, passed 100 tests.

let sortIsOrdered (xs: int list) =
    let sorted = List.sort xs
    sorted |> List.pairwise |> List.forall (fun (a, b) -> a <= b)
Check.Quick sortIsOrdered

// Conditional properties
let divisionProperty (x: int) (y: int) =
    y <> 0 ==> lazy (x = (x / y) * y + (x % y))
Check.Quick divisionProperty

// Custom generators
type Email = Email of string
let emailGen =
    gen {
        let! user = Gen.elements [ "ana"; "bo"; "cy" ]
        let! domain = Gen.elements [ "x.com"; "y.org" ]
        return Email $"{user}@{domain}"
    }
let emailArb = Arb.fromGen emailGen
Prop.forAll emailArb (fun (Email e) -> e.Contains "@") |> Check.Quick

// Model-based testing: compare an implementation with a simple model (e.g., a Map vs your custom dictionary)