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)