Persimmon.Dried


Practical Persimmon.Dried

Arbitray

You can use arbitraries flexibly: there are various arbitraries for basic types like Arb.int, Arb.byte and Arb.string, and for collection types like Arb.list Arb.int, Arb.array Arb.float and Arb.map Arb.int Arb.string.

You can also use arbitraries for functions or System.Func:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
// generate F# function (int -> int)
let ``function as parameter`` = Prop.forAll(Arb.func CoArb.int Arb.int)(fun f ->
  f (f 0) = (f >> f) 0
)

// generate System.Func<int, string>
let ``System.Func as parameter`` = Prop.forAll(Arb.systemFunc(CoArb.int, Arb.string))(fun f ->
  f.Invoke(0) = "0"
)

A module CoArb represents the module of “arbitraries for functions (which are arbitraries)”.

If you want to treat arbitraries of collection types as non-empty or non-null, you can:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
// non empty int list
let ``non empty list`` = Prop.forAll(Arb.nonEmpty(Arb.list Arb.int), Arb.int)(fun xs i ->
  List.head xs = i
)

// non null string []
let ``non null array`` = Prop.forAll(Arb.nonNull(Arb.array Arb.string))(fun xs ->
  Array.length xs >= 0
)

Arbitrary and Gen

When you use Persimmon.Dried, you may want to generate arguments as well as you think. You can make another arbitrary from the present one.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
// generate int list arguments whose length are no more than 3
let arbLs = {
  Gen = Gen.listOfMaxLength 3 <| Arb.int.Gen
  Shrinker = Shrink.shrinkList <| Arb.int.Shrinker
  PrettyPrinter = Pretty.prettyList
}

let ``list of max length 3`` = Prop.forAll(arbLs)(fun xs ->
  List.length xs <= 3
)

The basic arbitraries are F# records which have 3 values (labels), Gen, Shrinker and PrettyPrinter. In these labels, Gen : Gen<'T> takes charge of generating values, so controling it makes the result better.

Gen module has a lot of useful functions fo Gen<'T> values. There are examples in the following subsections.

oneOf

oneOf function takes a sequence of Gen<'T> then returns one of elements in it.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
// use one of Gen element in the sequence
let arbOneOf = {
  Arb.int with Gen = Gen.oneOf <| List.map Gen.constant [2; 3; 5; 7]
}

let ``get one of prime numbers`` = Prop.forAll(arbOneOf)(fun i ->
  let contains v = List.exists (fun x -> x = v)
  contains i [1 .. 10]    // [2; 3; 5; 7] ⊂ [1 .. 10]
)

suchThat

suchThat function takes Gen<'T> value and a condition function, then returns Gen<'T> to generate values which satisfy the condition.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
// generate values satisfying the condition
let arbEven = {
  Arb.int with Gen = Gen.suchThat (fun i -> i % 2 = 0) Arb.int.Gen  // filter only even number
}

let ``only even number`` = Prop.forAll(arbEven)(fun i ->
  (i * i) % 4 = 0
)

listOfLength

listOfLength function takes a length number N and Gen<'T>, then returns Gen<'T list> which generates N-length ‘T list.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
// generate int lists whose length is 3
let arbList3 = {
  Gen = Gen.listOfLength 3 <| Arb.int.Gen
  Shrinker = Shrink.shrinkList <| Arb.int.Shrinker
  PrettyPrinter = Pretty.prettyList
}

// generate int lists whose length is 5
let arbList5 = {
  arbList3 with Gen = Gen.listOfLength 5 <| Arb.int.Gen
}

let ``list of length`` = Prop.forAll(arbList3, arbList5)(fun xs3 xs5 ->
  List.length xs3 < List.length xs5
)

There are similar functions such as listOfMaxLength which generates lists less than or equal to length N, and listOfMinLength which generates lists greater than or equal to length N.

If you want to know more about Gen, let’s read the source code.

Properties

In Prop module, there are useful functions and operators to write properties.

And / Or operator

The And operator .&. succeeds if both of properties succeed, otherwise fails.

1: 
2: 
3: 
4: 
5: 
6: 
// lhs .&. lazy rhs succeeds both of lhs and rhs
let ``and operator`` = Prop.forAll(Arb.int, Arb.int)(fun x y ->
  x + 1 > x |@ "x + 1 is greater than x" .&.
  lazy (y - 1 < y |@ "y - 1 is less than y") .&.
  lazy (x * y > x + y |@ "x * y is greather than x + y")  // This property may fail
)

On the other hand, the Or operator .|. succeeds if either property succeeds, fails if both fail.

1: 
2: 
3: 
4: 
5: 
6: 
// lhs .|. lazy rhs succeeds either of lhs and rhs
let ``or operator`` = Prop.forAll(Arb.int, Arb.int)(fun x y ->
  "x + 1 is greater than x" @| (x + 1 > x) .|.
  lazy ("y - 1 is less than y" @| (y - 1 < y)) .|.
  lazy ("x * y is greather than x + y" @| (x * y > x + y))  // This property may fail
)

If you execute each 2 properties, and operator will fail and or operator will succeed.

|@ and @| you see in the code snipets above are the labeling operators. You can use them as p |@ s or s @| p, then label p s.

Conditional property

==> operator checks a right hand side property only if a left hand side property succeeds.

1: 
2: 
3: 
4: 
5: 
// lhs ==> lazy rhs checks rhs if lhs succeeds.
let ``conditional property`` = Prop.forAll(Arb.int, Arb.list Arb.int)(fun i ls ->
  let ils = List.Cons(i, ls)
  List.length ls > 5  ==> lazy (List.length ils > 6)
)

This snipet checks whether the length of i :: ls is greater than 6 or not if the input list ls has greater length than 5.

Note that the binary operators which take both properties, same as .&. or .|., must take its right hand side property as lazy. This restriction is for the reason that F# mainly uses eager evaluation. In the case of rasing an exception when the right property is evaluated, the exception is occurred at the time of passing both properties to the operator, then the check fails.

1: 
2: 
3: 
4: 
// What if we could...
let ``right hand side executes... what?`` = Prop.forAll(Arb.int)(fun i ->
  (i + 1 = i) ==> (i / 0 = 0)  // write like this?
)

Classify properties

classify function is similar to the labeling operators in the way of naming properties. Moreover, it can classify them by any conditions.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
let ``classifying test case`` = Prop.forAll(Arb.int, Arb.int)(fun x y ->
  x + y > x
  |> Prop.classify(x < 0, "x is negative")
  |> Prop.classify((x = 0), "x is zero")
  |> Prop.classify(y < 0, "y is negative")
  |> Prop.classify((y = 0), "y is zero")
)

When you execute the snipet above, it may show you as follows:

1: 
Runner.run "" prms ``classifying test case``
No output has been produced.

You can get the ratio of failing arguments from here.

val prms : obj

Full name: Practice.prms
val ( function as parameter ) : obj

Full name: Practice.( function as parameter )
Multiple items
val int : value:'T -> int (requires member op_Explicit)

Full name: Microsoft.FSharp.Core.Operators.int

--------------------
type int = int32

Full name: Microsoft.FSharp.Core.int

--------------------
type int<'Measure> = int

Full name: Microsoft.FSharp.Core.int<_>
val ( System.Func as parameter ) : obj

Full name: Practice.( System.Func as parameter )
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
val ( non empty list ) : obj

Full name: Practice.( non empty list )
type 'T list = List<'T>

Full name: Microsoft.FSharp.Collections.list<_>
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
val ( non null array ) : obj

Full name: Practice.( non null array )
type 'T array = 'T []

Full name: Microsoft.FSharp.Core.array<_>
module Array

from Microsoft.FSharp.Collections
val length : array:'T [] -> int

Full name: Microsoft.FSharp.Collections.Array.length
val arbLs : obj

Full name: Practice.arbLs
val ( list of max length 3 ) : obj

Full name: Practice.( list of max length 3 )
val arbOneOf : obj

Full name: Practice.arbOneOf
val ( get one of prime numbers ) : obj

Full name: Practice.( get one of prime numbers )
val arbEven : obj

Full name: Practice.arbEven
val ( only even number ) : obj

Full name: Practice.( only even number )
val arbList3 : obj

Full name: Practice.arbList3
val arbList5 : obj

Full name: Practice.arbList5
val ( list of length ) : obj

Full name: Practice.( list of length )
val ( and operator ) : obj

Full name: Practice.( and operator )
val ( or operator ) : obj

Full name: Practice.( or operator )
val ( conditional property ) : obj

Full name: Practice.( conditional property )
val ( right hand side executes... what? ) : obj

Full name: Practice.( right hand side executes... what? )
val ( classifying test case ) : obj

Full name: Practice.( classifying test case )
Fork me on GitHub