Jonathan Taylor
Scala Dev @ Cake Solutions*
[<Theory>]
[<InlineData(1, 1)>]
[<InlineData(21, 21)>]
[<InlineData(3, 4)>]
[<InlineData(4, 3)>]
[<InlineData(10, 0)>]
[<InlineData(0, 10)>]
let additionCommutative (a: int) (b: int) =
Assert.Equal(a + b, b + a)
[<Fact>]
let ``prop_additionCommutative``() =
FsCheck.Check.QuickThrowOnFailure <| fun (a: int) (b: int) ->
a + b = b + a
Random test case generation
[<Property>]
let ``prop_additionCommutative2``(a: int) (b: int) =
a + b = b + a
let rec qsort = function
| x :: xs ->
let lhs, rhs = List.partition (fun x' -> x' < x) xs
qsort lhs @ List.singleton x @ qsort rhs
| _ ->
[]
[<Property>]
let ``prop_idempotent`` (xs: int list) =
qsort (qsort xs) = qsort xs
[<Property>]
let ``prop_minimum`` (xs: int list) =
not (List.isEmpty xs) ==>
lazy (List.head (qsort xs) = minimum xs)
Thinking in terms of properties/laws
printfn "%A" (sample 5 5 (constant 42))
// [42; 42; 42; 42; 42]printfn "%A" (sample 5 5 (choose (1, 20)))
// [3; 4; 17; 18; 11]printfn "%A" (sample 5 5 (elements [1..10]))
// [1; 8; 3; 5; 4]printfn "%A" (sample 5 5 (oneof [
elements [1..10]
elements [11..20]
elements [21..30]
]))
// [14; 22; 26; 3; 16]printfn "%A" (sample 5 10 (frequency [
20, elements [1..10]
30, elements [11..20]
50, elements [21..30]
]))
// [10; 21; 19; 10; 30; 30; 21; 30; 27; 22]printfn "listOf: %A" (sample 5 5 (choose (1, 10) |> Gen.listOf))
// listOf: [[5]; []; [9; 4; 5]; [6; 9]; [3]]
printfn "listOfLength: %A" (sample 5 5 (choose (1, 10) |> Gen.listOfLength 5))
// listOfLength: [[2; 8; 8; 1; 6]; [4; 2; 8; 8; 1]; [10; 4; 2; 8; 8]; [10; 10; 4; 2; 8];
// [4; 10; 10; 4; 2]]
printfn "shuffle: %A" (sample 5 5 (shuffle [1;2;3;4;5]))
// shuffle: [[|1; 3; 2; 5; 4|]; [|4; 1; 3; 2; 5|]; [|4; 5; 2; 3; 1|]; [|2; 1; 5; 4; 3|];
// [|2; 3; 4; 5; 1|]]printfn "%A" (sample 5 5 (genInt |> where (fun n -> n > 10)))
// [12; 15; 11; 13; 19]type Arbitrary<'a>() =
abstract Generator : Gen<'a>
abstract Shrinker : 'a -> seq<'a>
Simplifying a counterexample
let s1 = Seq.singleton 42 |> Seq.map string
printfn "%A" s1
let l1 = List.singleton 42 |> List.map string
printfn "%A" l1
let a1 = Array.singleton 42 |> Array.map string
printfn "%A" a1
let g1 = Gen.constant 42 |> Gen.map string
printfn "%A" <| sample 0 1 g1
let fn n s1 s2 = String.length (sprintf "%d%s%s" n s1 s2) > 5
let gn = genInt
let gs1 = genInt |> map string
let gs2 = genInt |> map (fun n -> (string)(n * 10))
let g2 = map3 fn gn gs1 gs2
printfn "%A" <| sample 5 10 g2
let g3 = gen {
let! n = gn
let! s1 = gs1
let! s2 = gs2
return fn n s1 s2
}
printfn "%A" <| sample 5 10 g3
// Gen<(a -> b -> c -> d)> <*> Gen<a>
// Gen<(b -> c -> d)> <*> Gen<b>
// Gen<(c -> d)> <*> Gen<c>
// Gen<d>
let g4 = constant fn <*> gn <*> gs1 <*> gs2
printfn "%A" <| sample 5 10 g4
let g5 = map fn gn <*> gs1 <*> gs2
printfn "%A" <| sample 5 10 g5
let g6 = fn <!> gn <*> gs1 <*> gs2
printfn "%A" <| sample 5 10 g6
// Trying to implement Gen.oneof using 'map'.
let oneofv1 (gens: Gen<_> seq) =
choose(0, Seq.length gens - 1)
|> map (fun index -> Seq.item index gens)
let g7 = oneofv1 [constant 1; constant 2; constant 3]
printfn "g7: %A" <| sample 2 10 (g7 >>= id)
// Implementing Gen.oneof using '>>='.
let oneofv2 (gens: Gen<_> seq) =
choose(0, Seq.length gens - 1)
>>= (fun index -> Seq.item index gens)
let g8 = oneofv2 [constant 1; constant 2; constant 3]
printfn "g8: %A" <| sample 2 10 g8
// Implementing Gen.oneof using a 'gen' computation expression.
let oneofv3 (gens: Gen<_> seq) = gen {
let! index = choose(0, Seq.length gens - 1)
return! Seq.item index gens
}
let g9 = oneofv3 [constant 1; constant 2; constant 3]
printfn "g9: %A" <| sample 2 10 g9