layout: post
title: “Choosing properties for property-based testing”
description: “Or, I want to use FsCheck and Quickcheck, but I can never think of any properties to use”
categories: [“TDD”]

image: “/assets/img/property_commutative.png”

UPDATE: I did a talk on property-based testing based on these posts. Slides and video here.

In the previous post, I described the basics of property-based testing, and showed how it could save a lot of time by generating random tests.

But here’s a common problem. Everyone who sees a property-based testing tool like FsCheck or QuickCheck thinks that it is amazing… but
when it times come to start creating your own properties, the universal complaint is: “what properties should I use? I can’t think of any!”

The goal of this post is to show some common patterns that can help you discover the properties that are applicable to your code.

Categories for properties

In my experience, many properties can be discovered by using one of the seven approaches listed below.

This is by no means a comprehensive list, just the ones that have been most useful to me.
For a different perspective, check out the list of patterns that the PEX team at Microsoft have compiled.

“Different paths, same destination”

These kinds of properties are based on combining operations in different orders, but getting the same result.
For example, in the diagram below, doing X then Y gives the same result as doing Y followed by X.

Commutative property

The commutative property of addition is an obvious example of this pattern. For example, the result of add 1 then add 2 is the same as the result of add 2 followed by add 1.

This pattern, generalized, can produce a wide range of useful properties. We’ll see some more uses of this pattern later in this post.

“There and back again”

These kinds of properties are based on combining an operation with its inverse, ending up with the same value you started with.

In the diagram below, doing X serializes ABC to some kind of binary format, and the inverse of X is some sort of deserialization that returns the same ABC value again.

Inverse

In addition to serialization/deserialization, other pairs of operations can be checked this way: addition/subtraction, write/read, setProperty/getProperty, and so on.

Other pair of functions fit this pattern too, even though they are not strict inverses, pairs such as insert/contains, create/exists , etc.

“Some things never change”

These kinds of properties are based on an invariant that is preserved after some transformation.

In the diagram below, the transform changes the order of the items, but the same four items are still present afterwards.

Invariant

Common invariants include size of a collection (for map say), the contents of a collection (for sort say), the height or depth of something in proportion to size (e.g. balanced trees).

“The more things change, the more they stay the same”

These kinds of properties are based on “idempotence” — that is, doing an operation twice is the same as doing it once.

In the diagram below, using distinct to filter the set returns two items, but doing distinct twice returns the same set again.

Idempotence

Idempotence properties are very useful, and can be extended to things like database updates and message processing.

“Solve a smaller problem first”

These kinds of properties are based on “structural induction” — that is, if a large thing can be broken into smaller parts,
and some property is true for these smaller parts, then you can often prove that the property is true for a large thing as well.

In the diagram below, we can see that the four-item list can be partitioned into an item plus a three-item list, which in turn can be
partitioned into an item plus a two-item list. If we can prove the property holds for two-item list, then we can infer that it holds for the three-item list, and for the four-item list as well.

Induction

Induction properties are often naturally applicable to recursive structures such as lists and trees.

“Hard to prove, easy to verify”

Often an algorithm to find a result can be complicated, but verifying the answer is easy.

In the diagram below, we can see that finding a route through a maze is hard, but checking that it works is trivial!

Hard to find, easy to verify

Many famous problems are of this sort, such as prime number factorization. But this approach can be used for even simple problems.

For example, you might check that a string tokenizer works by just concatenating all the tokens again. The resulting string should be the same as what you started with.

“The test oracle”

In many situations you often have an alternate version of an algorithm or process (a “test oracle”) that you can use to check your results.

Test Oracle

For example, you might have a high-performance algorithm with optimization tweaks that you want to test. In this case,
you might compare it with a brute force algorithm that is much slower but is also much easier to write correctly.

Similarly, you might compare the result of a parallel or concurrent algorithm with the result of a linear, single thread version.

Putting the categories to work with some real examples

In this section, we’ll apply these categories to see if we can come up with properties for some simple functions such as “sort a list” and “reverse a list”.

“Different paths, same destination” applied to a list sort

Let’s start with “different paths, same destination” and apply it to a “list sort” function.

Can we think of any way of combining an operation before List.sort, and another operation after List.sort,
so that you should end up with the same result? That is, so that “going up then across the top” is the same as “going across the bottom then up”.

List sort?

How about this?

  • Path 1: We add one to each element of the list, then sort.
  • Path 2: We sort, then add one to each element of the list.
  • Both lists should be equal.

List sort with +1

Here’s some code that implements that property:

  1. let ``+1 then sort should be same as sort then +1`` sortFn aList =
  2. let add1 x = x + 1
  3. let result1 = aList |> sortFn |> List.map add1
  4. let result2 = aList |> List.map add1 |> sortFn
  5. result1 = result2
  6. // test
  7. let goodSort = List.sort
  8. Check.Quick (``+1 then sort should be same as sort then +1`` goodSort)
  9. // Ok, passed 100 tests.

Well, that works, but it also would work for a lot of other transformations too.
For example, if we implemented List.sort as just the identity, then this property would be satisfied equally well! You can test this for yourself:

  1. let badSort aList = aList
  2. Check.Quick (``+1 then sort should be same as sort then +1`` badSort)
  3. // Ok, passed 100 tests.

The problem with this property is that it is not exploiting any of the “sortedness”. We know that a sort will probably reorder a list, and certainly, the smallest element should be first.

How about adding an item that we know will come at the front of the list after sorting?

  • Path 1: We append Int32.MinValue to the end of the list, then sort.
  • Path 2: We sort, then prepend Int32.MinValue to the front of the list.
  • Both lists should be equal.

List sort with minValue

Here’s the code:

  1. let ``append minValue then sort should be same as sort then prepend minValue`` sortFn aList =
  2. let minValue = Int32.MinValue
  3. let appendThenSort = (aList @ [minValue]) |> sortFn
  4. let sortThenPrepend = minValue :: (aList |> sortFn)
  5. appendThenSort = sortThenPrepend
  6. // test
  7. Check.Quick (``append minValue then sort should be same as sort then prepend minValue`` goodSort)
  8. // Ok, passed 100 tests.

The bad implementation fails now!

  1. Check.Quick (``append minValue then sort should be same as sort then prepend minValue`` badSort)
  2. // Falsifiable, after 1 test (2 shrinks)
  3. // [0]

In other words, the bad sort of [0; minValue] is not the same as [minValue; 0].

So that’s good!

But… we’ve got some hard coded things in there that the Enterprise Developer From Hell (see previous post)
could take advantage of! The EDFH will exploit the fact that we always use Int32.MinValue and that we always prepend or append it to the test list.

In other words, the EDFH can identify which path we are on and have special cases for each one:

  1. // The Enterprise Developer From Hell strikes again
  2. let badSort2 aList =
  3. match aList with
  4. | [] -> []
  5. | _ ->
  6. let last::reversedTail = List.rev aList
  7. if (last = Int32.MinValue) then
  8. // if min is last, move to front
  9. let unreversedTail = List.rev reversedTail
  10. last :: unreversedTail
  11. else
  12. aList // leave alone

And when we check it…

  1. // Oh dear, the bad implementation passes!
  2. Check.Quick (``append minValue then sort should be same as sort then prepend minValue`` badSort2)
  3. // Ok, passed 100 tests.

We could fix this by (a) picking a random number smaller than any number in the list and (b) inserting it at a random location rather than always appending it.
But rather than getting too complicated, let’s stop and reconsider.

An alternative approach which also exploits the “sortedness” is to first negate all the values,
then on the path that negates after the sort, add an extra reverse as well.

List sort with negate

  1. let ``negate then sort should be same as sort then negate then reverse`` sortFn aList =
  2. let negate x = x * -1
  3. let negateThenSort = aList |> List.map negate |> sortFn
  4. let sortThenNegateAndReverse = aList |> sortFn |> List.map negate |> List.rev
  5. negateThenSort = sortThenNegateAndReverse

This property is harder for the EDFH to beat because there are no magic numbers to help identify which path you are on:

  1. // test
  2. Check.Quick ( ``negate then sort should be same as sort then negate then reverse`` goodSort)
  3. // Ok, passed 100 tests.
  4. // test
  5. Check.Quick ( ``negate then sort should be same as sort then negate then reverse`` badSort)
  6. // Falsifiable, after 1 test (1 shrinks)
  7. // [1; 0]
  8. // test
  9. Check.Quick ( ``negate then sort should be same as sort then negate then reverse`` badSort2)
  10. // Falsifiable, after 5 tests (3 shrinks)
  11. // [1; 0]

You might argue that we are only testing sorting for lists of integers. But the List.sort function is generic and knows nothing about integers per se,
so I have high confidence that this property does test the core sorting logic.

Applying “different paths, same destination” to a list reversal function

Ok, enough of List.sort. What about applying the same ideas to the list reversal function?

We can do the same append/prepend trick:

List reverse

Here’s the code for the property:

  1. let ``append any value then reverse should be same as reverse then prepend same value`` revFn anyValue aList =
  2. let appendThenReverse = (aList @ [anyValue]) |> revFn
  3. let reverseThenPrepend = anyValue :: (aList |> revFn)
  4. appendThenReverse = reverseThenPrepend

Here are the test results for the correct function and for two incorrect functions:

  1. // test
  2. let goodReverse = List.rev
  3. Check.Quick (``append any value then reverse should be same as reverse then prepend same value`` goodReverse)
  4. // Ok, passed 100 tests.
  5. // bad implementation fails
  6. let badReverse aList = []
  7. Check.Quick (``append any value then reverse should be same as reverse then prepend same value`` badReverse)
  8. // Falsifiable, after 1 test (2 shrinks)
  9. // true, []
  10. // bad implementation fails
  11. let badReverse2 aList = aList
  12. Check.Quick (``append any value then reverse should be same as reverse then prepend same value`` badReverse2)
  13. // Falsifiable, after 1 test (1 shrinks)
  14. // true, [false]

You might notice something interesting here. I never specified the type of the list. The property works with any list.

In cases like these, FsCheck will generate random lists of bools, strings, ints, etc.

In both failing cases, the anyValue is a bool. So FsCheck is using lists of bools to start with.

Here’s an exercise for you: Is this property good enough? Is there some way that the EDFH can create an implementation that will pass?

“There and back again”

Sometimes the multi-path style properties are not available or too complicated, so let’s look at some other approaches.

We’ll start with properties involving inverses.

Let’s start with list sorting again. Is there an inverse to sorting? Hmmm, not really. So we’ll skip sorting for now.

What about list reversal? Well, as it happens, reversal is its own inverse!

List reverse with inverse

Let’s turn that into a property:

  1. let ``reverse then reverse should be same as original`` revFn aList =
  2. let reverseThenReverse = aList |> revFn |> revFn
  3. reverseThenReverse = aList

And it passes:

  1. let goodReverse = List.rev
  2. Check.Quick (``reverse then reverse should be same as original`` goodReverse)
  3. // Ok, passed 100 tests.

Unfortunately, a bad implementation satisfies the property too!

  1. let badReverse aList = aList
  2. Check.Quick (``reverse then reverse should be same as original`` badReverse)
  3. // Ok, passed 100 tests.

Nevertheless, the use of properties involving inverses can be very useful to verify that your inverse function
(such as deserialization) does indeed “undo” the primary function (such as serialization).

We’ll see some real examples of using this in the next post.

“Hard to prove, easy to verify”

So far we’ve been testing properties without actually caring about the end result of an operation.

But of course in practice, we do care about the end result!

Now we normally can’t really tell if the result is right without duplicating the function under test.
But often we can tell that the result is wrong quite easily. In the maze diagram from above, we can easily check whether the path works or not.

If we are looking for the shortest path, we might not be able to check it, but at least we know that we have some valid path.

This principle can be applied quite generally.

For example, let’s say that we want to check whether a string split function is working. We don’t have to write a tokenizer — all we have to do is ensure that the tokens,
when concatenated, give us back the original string!

String split property

Here’s the core code from that property:

  1. let concatWithComma s t = s + "," + t
  2. let tokens = originalString.Split [| ',' |]
  3. let recombinedString =
  4. // can use reduce safely because there is always at least one token
  5. tokens |> Array.reduce concatWithComma
  6. // compare the result with the original
  7. originalString = recombinedString

But how can we create an original string? The random strings generated by FsCheck are unlikely to contain many commas!

There are ways that you can control exactly how FsCheck generates random data, which we’ll look at later.

For now though, we’ll use a trick. The trick is to let FsCheck generate a list of random strings, and then we’ll build an originalString from them by concatting them together.

So here’s the complete code for the property:

  1. let ``concatting the elements of a string split by commas recreates the original string`` aListOfStrings =
  2. // helper to make a string
  3. let addWithComma s t = s + "," + t
  4. let originalString = aListOfStrings |> List.fold addWithComma ""
  5. // now for the property
  6. let tokens = originalString.Split [| ',' |]
  7. let recombinedString =
  8. // can use reduce safely because there is always at least one token
  9. tokens |> Array.reduce addWithComma
  10. // compare the result with the original
  11. originalString = recombinedString

When we test this we are happy:

  1. Check.Quick ``concatting the elements of a string split by commas recreates the original string``
  2. // Ok, passed 100 tests.

“Hard to prove, easy to verify” for list sorting

So how can we apply this principle to a sorted list? What property is easy to verify?

The first thing that pops into my mind is that for each pair of elements in the list, the first one will be smaller than the second.

Pairwise property

So let’s make that into a property:

  1. let ``adjacent pairs from a list should be ordered`` sortFn aList =
  2. let pairs = aList |> sortFn |> Seq.pairwise
  3. pairs |> Seq.forall (fun (x,y) -> x <= y )

But something funny happens when we try to check it. We get an error!

  1. let goodSort = List.sort
  2. Check.Quick (``adjacent pairs from a list should be ordered`` goodSort)
  1. System.Exception: Geneflect: type not handled System.IComparable
  2. at FsCheck.ReflectArbitrary.reflectObj@102-4.Invoke(String message)
  3. at Microsoft.FSharp.Core.PrintfImpl.go@523-3[b,c,d](String fmt, Int32 len, FSharpFunc`2 outputChar, FSharpFunc`2 outa, b os, FSharpFunc`2 finalize, FSharpList`1 args, Int32 i)
  4. at Microsoft.FSharp.Core.PrintfImpl.run@521[b,c,d](FSharpFunc`2 initialize, String fmt, Int32 len, FSharpList`1 args)

What does System.Exception: type not handled System.IComparable mean? It means that FsCheck is trying to generate a random list, but all it knows is that the elements must be IComparable.
But IComparable is not a type than can be instantiated, so FsCheck throws an error.

How can we prevent this from happening? The solution is to specify a particular type for the property, such as int list, like this:

  1. let ``adjacent pairs from a list should be ordered`` sortFn (aList:int list) =
  2. let pairs = aList |> sortFn |> Seq.pairwise
  3. pairs |> Seq.forall (fun (x,y) -> x <= y )

This code works now.

  1. let goodSort = List.sort
  2. Check.Quick (``adjacent pairs from a list should be ordered`` goodSort)
  3. // Ok, passed 100 tests.

Note that even though the property has been constrained, the property is still a very general one. We could have used string list instead, for example, and it would work just the same.

  1. let ``adjacent pairs from a string list should be ordered`` sortFn (aList:string list) =
  2. let pairs = aList |> sortFn |> Seq.pairwise
  3. pairs |> Seq.forall (fun (x,y) -> x <= y )
  4. Check.Quick (``adjacent pairs from a string list should be ordered`` goodSort)
  5. // Ok, passed 100 tests.

TIP: If FsCheck throws “type not handled”, add explicit type constraints to your property

Are we done now? No! One problem with this property is that it doesn’t catch malicious implementations by the EDFH.

  1. // bad implementation passes
  2. let badSort aList = []
  3. Check.Quick (``adjacent pairs from a list should be ordered`` badSort)
  4. // Ok, passed 100 tests.

Is it a surprise to you that a silly implementation also works?

Hmmm. That tells us that there must be some property other than pairwise ordering associated with sorting that we’ve overlooked. What are we missing here?

This is a good example of how doing property-based testing can lead to insights about design. We thought we knew what sorting meant, but we’re being forced to be a bit stricter in our definition.

As it happens, we’ll fix this particular problem by using the next principle!

“Some things never change”

A useful kind of property is based on an invariant that is preserved after some transformation, such as preserving length or contents.

They are not normally sufficient in themselves to ensure a correct implementation, but they do often act as a counter-check to more general properties.

For example, in the previous post, we created commutative and associative properties for addition, but then noticed that simply having
an implementation that returned zero would satisfy them just as well! It was only when we added x + 0 = x as a property that we could eliminate that particular malicious implementation.

And in the “list sort” example above, we could satisfy the “pairwise ordered” property with a function that just returned an empty list! How could we fix that?

Our first attempt might be to check the length of the sorted list. If the lengths are different, then the sort function obviously cheated!

  1. let ``sort should have same length as original`` sortFn (aList:int list) =
  2. let sorted = aList |> sortFn
  3. List.length sorted = List.length aList

We check it and it works:

  1. let goodSort = List.sort
  2. Check.Quick (``sort should have same length as original`` goodSort )
  3. // Ok, passed 100 tests.

And yes, the bad implementation fails:

  1. let badSort aList = []
  2. Check.Quick (``sort should have same length as original`` badSort )
  3. // Falsifiable, after 1 test (1 shrink)
  4. // [0]

Unfortunately, the BDFH is not defeated and can come up with another compliant implementation! Just repeat the first element N times!

  1. // bad implementation has same length
  2. let badSort2 aList =
  3. match aList with
  4. | [] -> []
  5. | head::_ -> List.replicate (List.length aList) head
  6. // for example
  7. // badSort2 [1;2;3] => [1;1;1]

Now when we test this, it passes:

  1. Check.Quick (``sort should have same length as original`` badSort2)
  2. // Ok, passed 100 tests.

What’s more, it also satisfies the pairwise property too!

  1. Check.Quick (``adjacent pairs from a list should be ordered`` badSort2)
  2. // Ok, passed 100 tests.

Sort invariant - 2nd attempt

So now we have to try again. What is the difference between the real result [1;2;3] and the fake result [1;1;1]?

Answer: the fake result is throwing away data. The real result always contains the same contents as the original list, but just in a different order.

Permutation property

That leads us to a new property: a sorted list is always a permutation of the original list. Aha! Let’s write the property in terms of permutations now:

  1. let ``a sorted list is always a permutation of the original list`` sortFn (aList:int list) =
  2. let sorted = aList |> sortFn
  3. let permutationsOfOriginalList = permutations aList
  4. // the sorted list must be in the seq of permutations
  5. permutationsOfOriginalList
  6. |> Seq.exists (fun permutation -> permutation = sorted)

Great, now all we need is a permutation function.

Let’s head over to StackOverflow and steal borrow an implementation. Here it is:

  1. /// given aList and anElement to insert,
  2. /// generate all possible lists with anElement
  3. /// inserted into aList
  4. let rec insertElement anElement aList =
  5. // From http://stackoverflow.com/a/4610704/1136133
  6. seq {
  7. match aList with
  8. // empty returns a singleton
  9. | [] -> yield [anElement]
  10. // not empty?
  11. | first::rest ->
  12. // return anElement prepended to the list
  13. yield anElement::aList
  14. // also return first prepended to all the sublists
  15. for sublist in insertElement anElement rest do
  16. yield first::sublist
  17. }
  18. /// Given a list, return all permutations of it
  19. let rec permutations aList =
  20. seq {
  21. match aList with
  22. | [] -> yield []
  23. | first::rest ->
  24. // for each sub-permutation,
  25. // return the first inserted into it somewhere
  26. for sublist in permutations rest do
  27. yield! insertElement first sublist
  28. }

Some quick interactive tests confirm that it works as expected:

  1. permutations ['a';'b';'c'] |> Seq.toList
  2. // [['a'; 'b'; 'c']; ['b'; 'a'; 'c']; ['b'; 'c'; 'a']; ['a'; 'c'; 'b'];
  3. // ['c'; 'a'; 'b']; ['c'; 'b'; 'a']]
  4. permutations ['a';'b';'c';'d'] |> Seq.toList
  5. // [['a'; 'b'; 'c'; 'd']; ['b'; 'a'; 'c'; 'd']; ['b'; 'c'; 'a'; 'd'];
  6. // ['b'; 'c'; 'd'; 'a']; ['a'; 'c'; 'b'; 'd']; ['c'; 'a'; 'b'; 'd'];
  7. // ['c'; 'b'; 'a'; 'd']; ['c'; 'b'; 'd'; 'a']; ['a'; 'c'; 'd'; 'b'];
  8. // ['c'; 'a'; 'd'; 'b']; ['c'; 'd'; 'a'; 'b']; ['c'; 'd'; 'b'; 'a'];
  9. // ['a'; 'b'; 'd'; 'c']; ['b'; 'a'; 'd'; 'c']; ['b'; 'd'; 'a'; 'c'];
  10. // ['b'; 'd'; 'c'; 'a']; ['a'; 'd'; 'b'; 'c']; ['d'; 'a'; 'b'; 'c'];
  11. // ['d'; 'b'; 'a'; 'c']; ['d'; 'b'; 'c'; 'a']; ['a'; 'd'; 'c'; 'b'];
  12. // ['d'; 'a'; 'c'; 'b']; ['d'; 'c'; 'a'; 'b']; ['d'; 'c'; 'b'; 'a']]
  13. permutations [3;3] |> Seq.toList
  14. // [[3; 3]; [3; 3]]

Excellent! Now let’s run FsCheck:

  1. Check.Quick (``a sorted list is always a permutation of the original list`` goodSort)

Hmmm. That’s funny, nothing seems to be happening. And my CPU is maxing out for some reason. What’s going on?

What’s going on is that you are going to be sitting there for a long time! If you are following along at home, I suggest you right-click and cancel the interactive session now.

The innocent looking permutations is really really slow for any normal sized list.
For example, a list of just 10 items has 3,628,800 permutations. While with 20 items, you are getting to astronomical numbers.

And of course, FsCheck will be doing hundreds of these tests! So this leads to an important tip:

TIP: Make sure your property checks are very fast. You will be running them a LOT!

We’ve already seen that even in the best case, FsCheck will evaluate the property 100 times. And if shrinking is needed, even more.
So make sure your tests are fast to run!

But what happens if you are dealing with real systems such as databases, networks, or other slow dependencies?

In his (highly recommended) video on using QuickCheck, John Hughes tells of
when his team was trying to detect flaws in a distributed data store that could be caused by network partitions and node failures.

Of course, killing real nodes thousands of times was too slow, so they extracted the core logic into a virtual model, and tested that instead.
As a result, the code was later refactored to make this kind of testing easier. In other words, property-based testing influenced the design of the code, just as TDD would.

Sort invariant - 3rd attempt

Ok, so we can’t use permutations by just looping through them. So let’s use the same idea but write a function that is specific for this case, a isPermutationOf function.

  1. let ``a sorted list has same contents as the original list`` sortFn (aList:int list) =
  2. let sorted = aList |> sortFn
  3. isPermutationOf aList sorted

Here’s the code for isPermutationOf and its associated helper functions:

  1. /// Given an element and a list, and other elements previously skipped,
  2. /// return a new list without the specified element.
  3. /// If not found, return None
  4. let rec withoutElementRec anElement aList skipped =
  5. match aList with
  6. | [] -> None
  7. | head::tail when anElement = head ->
  8. // matched, so create a new list from the skipped and the remaining
  9. // and return it
  10. let skipped' = List.rev skipped
  11. Some (skipped' @ tail)
  12. | head::tail ->
  13. // no match, so prepend head to the skipped and recurse
  14. let skipped' = head :: skipped
  15. withoutElementRec anElement tail skipped'
  16. /// Given an element and a list
  17. /// return a new list without the specified element.
  18. /// If not found, return None
  19. let withoutElement x aList =
  20. withoutElementRec x aList []
  21. /// Given two lists, return true if they have the same contents
  22. /// regardless of order
  23. let rec isPermutationOf list1 list2 =
  24. match list1 with
  25. | [] -> List.isEmpty list2 // if both empty, true
  26. | h1::t1 ->
  27. match withoutElement h1 list2 with
  28. | None -> false
  29. | Some t2 ->
  30. isPermutationOf t1 t2

Let’s try the test again. And yes, this time it completes before the heat death of the universe.

  1. Check.Quick (``a sorted list has same contents as the original list`` goodSort)
  2. // Ok, passed 100 tests.

What’s also great is that the malicious implementation now fails to satisfy this property!

  1. Check.Quick (``a sorted list has same contents as the original list`` badSort2)
  2. // Falsifiable, after 2 tests (5 shrinks)
  3. // [1; 0]

In fact, these two properties, adjacent pairs from a list should be ordered and a sorted list has same contents as the original list should indeed ensure that
any implementation is correct.

Sidebar: Combining properties

Just above, we noted that there were two properties needed to define the “is sorted” property. It would be nice if we could combine them into one property
is sorted so that we can have a single test.

Well, of course we can always merge the two sets of code into one function, but it’s preferable to keep functions as small as possible.
Furthermore, a property like has same contents might be reusable in other contexts as well.

What we want then, is an equivalent to AND and OR that is designed to work with properties.

FsCheck to the rescue! There are built in operators to combine properties: .&. for AND and .|. for OR.

Here is an example of them in use:

  1. let ``list is sorted``sortFn (aList:int list) =
  2. let prop1 = ``adjacent pairs from a list should be ordered`` sortFn aList
  3. let prop2 = ``a sorted list has same contents as the original list`` sortFn aList
  4. prop1 .&. prop2

When we test the combined property with a good implementation of sort, everything works as expected.

  1. let goodSort = List.sort
  2. Check.Quick (``list is sorted`` goodSort )
  3. // Ok, passed 100 tests.

And if we test a bad implementation, the combined property fails as well.

  1. let badSort aList = []
  2. Check.Quick (``list is sorted`` badSort )
  3. // Falsifiable, after 1 test (0 shrinks)
  4. // [0]

But there’s a problem now. Which of the two properties failed?

What we would like to do is add a “label” to each property so that we can tell them apart. In FsCheck, this is done with the |@ operator:

  1. let ``list is sorted (labelled)``sortFn (aList:int list) =
  2. let prop1 = ``adjacent pairs from a list should be ordered`` sortFn aList
  3. |@ "adjacent pairs from a list should be ordered"
  4. let prop2 = ``a sorted list has same contents as the original list`` sortFn aList
  5. |@ "a sorted list has same contents as the original list"
  6. prop1 .&. prop2

And now, when we test with the bad sort, we get a message Label of failing property: a sorted list has same contents as the original list:

  1. Check.Quick (``list is sorted (labelled)`` badSort )
  2. // Falsifiable, after 1 test (2 shrinks)
  3. // Label of failing property: a sorted list has same contents as the original list
  4. // [0]

For more on these operators, see the FsCheck documentation under “And, Or and Labels”.

And now, back to the property-divising strategies.

“Solving a smaller problem”

Sometimes you have a recursive data structure or a recursive problem. In these cases, you can often find a property that is true of a smaller part.

For example, for a sort, we could say something like:

  1. A list is sorted if:
  2. * The first element is smaller (or equal to) the second.
  3. * The rest of the elements after the first element are also sorted.

Here is that logic expressed in code:

  1. let rec ``First element is <= than second, and tail is also sorted`` sortFn (aList:int list) =
  2. let sortedList = aList |> sortFn
  3. match sortedList with
  4. | [] -> true
  5. | [first] -> true
  6. | [first;second] ->
  7. first <= second
  8. | first::second::tail ->
  9. first <= second &&
  10. let subList = second::tail
  11. ``First element is <= than second, and tail is also sorted`` sortFn subList

This property is satisfied by the real sort function:

  1. let goodSort = List.sort
  2. Check.Quick (``First element is <= than second, and tail is also sorted`` goodSort )
  3. // Ok, passed 100 tests.

But unfortunately, just like previous examples, the malicious implementations also pass.

  1. let badSort aList = []
  2. Check.Quick (``First element is <= than second, and tail is also sorted`` badSort )
  3. // Ok, passed 100 tests.
  4. let badSort2 aList =
  5. match aList with
  6. | [] -> []
  7. | head::_ -> List.replicate (List.length aList) head
  8. Check.Quick (``First element is <= than second, and tail is also sorted`` badSort2)
  9. // Ok, passed 100 tests.

So as before, we’ll need another property (such as the has same contents invariant) to ensure that the code is correct.

If you do have a recursive data structure, then try looking for recursive properties. They are pretty obvious and low hanging, when you get the hang of it.

Is the EDFH really a problem?

In the last few examples, I’ve noted that trivial but wrong implementations often satisfy the properties as well as good implementations.

But should we really spend time worrying about this? I mean, if we ever really released a sort algorithm that just duplicated the first element it would be obvious immediately, surely?

So yes, it’s true that truly malicious implementations are unlikely to be a problem. On the other hand, you should think of property-based testing not as a testing process, but as a design
process — a technique that helps you clarify what your system is really trying to do. And if a key aspect of your design is satisfied with just a simple implementation,
then perhaps there is something you have overlooked — something that, when you discover it, will make your design both clearer and more robust.

“The more things change, the more they stay the same”

Our next type of property is “idempotence”. Idempotence simply means that doing something twice is the same as doing it once.
If I tell you to “sit down” and then tell you to “sit down” again, the second command has no effect.

Idempotence is essential for reliable systems
and is a key aspect of service oriented and message-based architectures.

If you are designing these kinds of real-world systems it is well worth ensuring that your requests and processes are idempotent.

I won’t go too much into this right now, but let’s look at two simple examples.

First, our old friend sort is idempotent (ignoring stability) while reverse is not, obviously.

  1. let ``sorting twice gives the same result as sorting once`` sortFn (aList:int list) =
  2. let sortedOnce = aList |> sortFn
  3. let sortedTwice = aList |> sortFn |> sortFn
  4. sortedOnce = sortedTwice
  5. // test
  6. let goodSort = List.sort
  7. Check.Quick (``sorting twice gives the same result as sorting once`` goodSort )
  8. // Ok, passed 100 tests.

In general, any kind of query should be idempotent, or to put it another way: “asking a question should not change the answer”.

In the real world, this may not be the case. A simple query on a datastore run at different times may give different results.

Here’s a quick demonstration.

First we’ll create a NonIdempotentService that gives different results on each query.

  1. type NonIdempotentService() =
  2. let mutable data = 0
  3. member this.Get() =
  4. data
  5. member this.Set value =
  6. data <- value
  7. let ``querying NonIdempotentService after update gives the same result`` value1 value2 =
  8. let service = NonIdempotentService()
  9. service.Set value1
  10. // first GET
  11. let get1 = service.Get()
  12. // another task updates the data store
  13. service.Set value2
  14. // second GET called just like first time
  15. let get2 = service.Get()
  16. get1 = get2

But if we test it now, we find that it does not satisfy the required idempotence property:

  1. Check.Quick ``querying NonIdempotentService after update gives the same result``
  2. // Falsifiable, after 2 tests

As an alternative, we can create a (crude) IdempotentService that requires a timestamp for each transaction.
In this design, multiple GETs using the same timestamp will always retrieve the same data.

  1. type IdempotentService() =
  2. let mutable data = Map.empty
  3. member this.GetAsOf (dt:DateTime) =
  4. data |> Map.find dt
  5. member this.SetAsOf (dt:DateTime) value =
  6. data <- data |> Map.add dt value
  7. let ``querying IdempotentService after update gives the same result`` value1 value2 =
  8. let service = IdempotentService()
  9. let dt1 = DateTime.Now.AddMinutes(-1.0)
  10. service.SetAsOf dt1 value1
  11. // first GET
  12. let get1 = service.GetAsOf dt1
  13. // another task updates the data store
  14. let dt2 = DateTime.Now
  15. service.SetAsOf dt2 value2
  16. // second GET called just like first time
  17. let get2 = service.GetAsOf dt1
  18. get1 = get2

And this one works:

  1. Check.Quick ``querying IdempotentService after update gives the same result``
  2. // Ok, passed 100 tests.

So, if you are building a REST GET handler or a database query service, and you want idempotence, you should consider using techniques such as etags, “as-of” times, date ranges, etc.

If you need tips on how to do this, searching for idempotency patterns will turn up some good results.

“Two heads are better than one”

And finally, last but not least, we come to the “test oracle”. A test oracle is simply an alternative implementation that gives the right answer, and that you can check your results against.

Often the test oracle implementation is not suitable for production — it’s too slow, or it doesn’t parallelize, or it’s too poetic, etc.,
but that doesn’t stop it being very useful for testing.

So for “list sort”, there are many simple but slow implementations around. For example, here’s a quick implementation of insertion sort:

  1. module InsertionSort =
  2. // Insert a new element into a list by looping over the list.
  3. // As soon as you find a larger element, insert in front of it
  4. let rec insert newElem list =
  5. match list with
  6. | head::tail when newElem > head ->
  7. head :: insert newElem tail
  8. | other -> // including empty list
  9. newElem :: other
  10. // Sorts a list by inserting the head into the rest of the list
  11. // after the rest have been sorted
  12. let rec sort list =
  13. match list with
  14. | [] -> []
  15. | head::tail ->
  16. insert head (sort tail)
  17. // test
  18. // insertionSort [5;3;2;1;1]

With this in place, we can write a property that tests the result against insertion sort.

  1. let ``sort should give same result as insertion sort`` sortFn (aList:int list) =
  2. let sorted1 = aList |> sortFn
  3. let sorted2 = aList |> InsertionSort.sort
  4. sorted1 = sorted2

When we test the good sort, it works. Good!

  1. let goodSort = List.sort
  2. Check.Quick (``sort should give same result as insertion sort`` goodSort)
  3. // Ok, passed 100 tests.

And when we test a bad sort, it doesn’t. Even better!

  1. let badSort aList = aList
  2. Check.Quick (``sort should give same result as insertion sort`` badSort)
  3. // Falsifiable, after 4 tests (6 shrinks)
  4. // [1; 0]

Generating Roman numerals in two different ways

We can also use the test oracle approach to cross-check two different implementations when you’re not sure that either implementation is right!

For example, in my post “Commentary on ‘Roman Numerals Kata with Commentary’” I came up with two completely different algorithms for generating Roman Numerals.
Can we compare them to each other and test them both in one fell swoop?

The first algorithm was based on understanding that Roman numerals were based on tallying, leading to this simple code:

  1. let arabicToRomanUsingTallying arabic =
  2. (String.replicate arabic "I")
  3. .Replace("IIIII","V")
  4. .Replace("VV","X")
  5. .Replace("XXXXX","L")
  6. .Replace("LL","C")
  7. .Replace("CCCCC","D")
  8. .Replace("DD","M")
  9. // optional substitutions
  10. .Replace("IIII","IV")
  11. .Replace("VIV","IX")
  12. .Replace("XXXX","XL")
  13. .Replace("LXL","XC")
  14. .Replace("CCCC","CD")
  15. .Replace("DCD","CM")

Another way to think about Roman numerals is to imagine an abacus. Each wire has four “unit” beads and one “five” bead.

This leads to the so-called “bi-quinary” approach:

  1. let biQuinaryDigits place (unit,five,ten) arabic =
  2. let digit = arabic % (10*place) / place
  3. match digit with
  4. | 0 -> ""
  5. | 1 -> unit
  6. | 2 -> unit + unit
  7. | 3 -> unit + unit + unit
  8. | 4 -> unit + five // changed to be one less than five
  9. | 5 -> five
  10. | 6 -> five + unit
  11. | 7 -> five + unit + unit
  12. | 8 -> five + unit + unit + unit
  13. | 9 -> unit + ten // changed to be one less than ten
  14. | _ -> failwith "Expected 0-9 only"
  15. let arabicToRomanUsingBiQuinary arabic =
  16. let units = biQuinaryDigits 1 ("I","V","X") arabic
  17. let tens = biQuinaryDigits 10 ("X","L","C") arabic
  18. let hundreds = biQuinaryDigits 100 ("C","D","M") arabic
  19. let thousands = biQuinaryDigits 1000 ("M","?","?") arabic
  20. thousands + hundreds + tens + units

We now have two completely different algorithms, and we can cross-check them with each other to see if they give the same result.

  1. let ``biquinary should give same result as tallying`` arabic =
  2. let tallyResult = arabicToRomanUsingTallying arabic
  3. let biquinaryResult = arabicToRomanUsingBiQuinary arabic
  4. tallyResult = biquinaryResult

But if we try running this code, we get a ArgumentException: The input must be non-negative due to the String.replicate call.

  1. Check.Quick ``biquinary should give same result as tallying``
  2. // ArgumentException: The input must be non-negative.

So we need to only include inputs that are positive. We also need to exclude numbers that are greater than 4000, say, since the algorithms break down there too.

How can we implement this filter?

We saw in the previous post that we could use preconditions. But for this example, we’ll try something different and change the generator.

First we’ll define a new arbitrary integer called arabicNumber which is filtered as we want
(an “arbitrary” is a combination of a generator algorithm and a shrinker algorithm, as described in the previous post).

  1. let arabicNumber = Arb.Default.Int32() |> Arb.filter (fun i -> i > 0 && i <= 4000)

Next, we create a new property which is constrained to only use “arabicNumber” by using the Prop.forAll helper.

We’ll give the property the rather clever name of “for all values of arabicNumber, biquinary should give same result as tallying”.

  1. let ``for all values of arabicNumber biquinary should give same result as tallying`` =
  2. Prop.forAll arabicNumber ``biquinary should give same result as tallying``

Now finally, we can do the cross-check test:

  1. Check.Quick ``for all values of arabicNumber biquinary should give same result as tallying``
  2. // Ok, passed 100 tests.

And we’re good! Both algorithms work correctly, it seems.

“Model-based” testing

“Model-based” testing, which we will discuss in more detail in a later post, is a variant on having a test oracle.

The way it works is that, in parallel with your (complex) system under test, you create a simplified model.

Then, when you do something to the system under test, you do the same (but simplified) thing
to your model.

At the end, you compare your model’s state with the state of the system under test. If they are the same, you’re done. If not, either your SUT is buggy or your model is wrong and you have to start over!

Interlude: A game based on finding properties

With that, we have come to the end of the various property categories. We’ll go over them one more time in a minute — but first, an interlude.

If you sometimes feel that trying to find properties is a mental challenge, you’re not alone. Would it help to pretend that it is a game?

As it happens, there is a game based on property-based testing.

It’s called Zendo and it involves placing sets of objects (such as plastic pyramids) on a table,
such that each layout conforms to a pattern — a rule — or as we would now say, a property!.

The other players then have to guess what the rule (property) is, based on what they can see.

Here’s a picture of a Zendo game in progress:

Zendo

The white stones mean the property has been satisfied, while black stones mean failure. Can you guess the rule here?
I’m going to guess that it’s something like “a set must have a yellow pyramid that’s not touching the ground”.

Alright, I suppose Zendo wasn’t really inspired by property-based testing, but it is a fun game, and it has even been known to make an
appearance at programming conferences.

If you want to learn more about Zendo, the rules are here.

Applying the categories one more time

With all these categories in hand, let’s look at one more example problem, and see if we can find properties for it.

This sample is based on the well-known Dollar example described in Kent Beck’s “TDD By Example” book.

Nat Pryce, of Growing Object-Oriented Software Guided by Tests fame,
wrote a blog post about property-based testing a while ago (“Exploring Test-Driven Development with QuickCheck”).

In it, he expressed some frustration about property-based testing being useful in practice. So let’s revisit the example he referenced and see what we can do with it.

We’re not going to attempt to critique the design itself and make it more type-driven — others have done that.
Instead, we’ll take the design as given and see what properties we can come up with.

So what do we have?

  • A Dollar class that stores an Amount.
  • Methods Add and Times that transform the amount in the obvious way.
  1. // OO style class with members
  2. type Dollar(amount:int) =
  3. member val Amount = amount with get, set
  4. member this.Add add =
  5. this.Amount <- this.Amount + add
  6. member this.Times multiplier =
  7. this.Amount <- this.Amount * multiplier
  8. static member Create amount =
  9. Dollar amount

So, first let’s try it out interactively to make sure it works as expected:

  1. let d = Dollar.Create 2
  2. d.Amount // 2
  3. d.Times 3
  4. d.Amount // 6
  5. d.Add 1
  6. d.Amount // 7

But that’s just playing around, not real testing. So what kind of properties can we think of?

Let’s run through them all again:

  • Different paths to same result
  • Inverses
  • Invariants
  • Idempotence
  • Structural induction
  • Easy to verify
  • Test oracle

Let’s skip the “different paths” one for now. What about inverses? Are there any inverses we can use?

Yes, the setter and getter form an inverse that we can create a property from:

  1. let ``set then get should give same result`` value =
  2. let obj = Dollar.Create 0
  3. obj.Amount <- value
  4. let newValue = obj.Amount
  5. value = newValue
  6. Check.Quick ``set then get should give same result``
  7. // Ok, passed 100 tests.

Idempotence is relevant too. For example, doing two sets in a row should be the same as doing just one.
Here’s a property for that:

  1. let ``set amount is idempotent`` value =
  2. let obj = Dollar.Create 0
  3. obj.Amount <- value
  4. let afterFirstSet = obj.Amount
  5. obj.Amount <- value
  6. let afterSecondSet = obj.Amount
  7. afterFirstSet = afterSecondSet
  8. Check.Quick ``set amount is idempotent``
  9. // Ok, passed 100 tests.

Any “structural induction” properties? No, not relevant to this case.

Any “easy to verify” properties? Not anything obvious.

Finally, is there a test oracle? No. Again not relevant, although if we really were designing a complex currency management system,
it might be very useful to cross-check our results with a third party system.

Properties for an immutable Dollar

A confession! I cheated a bit in the code above and created a mutable class, which is how most OO objects are designed.

But in “TDD by Example” , Kent quickly realizes the problems with that and changes it to an immutable class, so let me do the same.

Here’s the immutable version:

  1. type Dollar(amount:int) =
  2. member val Amount = amount
  3. member this.Add add =
  4. Dollar (amount + add)
  5. member this.Times multiplier =
  6. Dollar (amount * multiplier)
  7. static member Create amount =
  8. Dollar amount
  9. // interactive test
  10. let d1 = Dollar.Create 2
  11. d1.Amount // 2
  12. let d2 = d1.Times 3
  13. d2.Amount // 6
  14. let d3 = d2.Add 1
  15. d3.Amount // 7

What’s nice about immutable code is that we can eliminate the need for testing of setters, so the two properties we just created have now become irrelevant!

To tell the truth they were pretty trivial anyway, so it’s no great loss.

So then, what new properties can we devise now?

Let’s look at the Times method. How can we test that? Which one of the strategies can we use?

I think the “different paths to same result” is very applicable. We can do the same thing we did with “sort” and do a times operation both “inside” and “outside” and see if they give the same result.

Dollar times

Here’s that property expressed in code:

  1. let ``create then times should be same as times then create`` start multiplier =
  2. let d0 = Dollar.Create start
  3. let d1 = d0.Times(multiplier)
  4. let d2 = Dollar.Create (start * multiplier)
  5. d1 = d2

Great! Let’s see if it works!

  1. Check.Quick ``create then times should be same as times then create``
  2. // Falsifiable, after 1 test

Oops — it doesn’t work!

Why not? Because we forgot that Dollar is a reference type and doesn’t compare equal by default!

As a result of this mistake, we have discovered a property that we might have overlooked!
Let’s encode that before we forget.

  1. let ``dollars with same amount must be equal`` amount =
  2. let d1 = Dollar.Create amount
  3. let d2 = Dollar.Create amount
  4. d1 = d2
  5. Check.Quick ``dollars with same amount must be equal``
  6. // Falsifiable, after 1 test

So now we need to fix this by adding support for IEquatable and so on.

You can do that if you like — I’m going to switch to F# record types and get equality for free!

Dollar properties — version 3

Here’s the Dollar rewritten again:

  1. type Dollar = {amount:int }
  2. with
  3. member this.Add add =
  4. {amount = this.amount + add }
  5. member this.Times multiplier =
  6. {amount = this.amount * multiplier }
  7. static member Create amount =
  8. {amount=amount}

And now our two properties are satisfied:

  1. Check.Quick ``dollars with same amount must be equal``
  2. // Ok, passed 100 tests.
  3. Check.Quick ``create then times should be same as times then create``
  4. // Ok, passed 100 tests.

We can extend this approach for different paths. For example, we can extract the amount and compare it directly, like this:

Dollar times

The code looks like this:

  1. let ``create then times then get should be same as times`` start multiplier =
  2. let d0 = Dollar.Create start
  3. let d1 = d0.Times(multiplier)
  4. let a1 = d1.amount
  5. let a2 = start * multiplier
  6. a1 = a2
  7. Check.Quick ``create then times then get should be same as times``
  8. // Ok, passed 100 tests.

And we can also include Add in the mix as well.

For example, we can do a Times followed by an Add via two different paths, like this:

Dollar times

And here’s the code:

  1. let ``create then times then add should be same as times then add then create`` start multiplier adder =
  2. let d0 = Dollar.Create start
  3. let d1 = d0.Times(multiplier)
  4. let d2 = d1.Add(adder)
  5. let directAmount = (start * multiplier) + adder
  6. let d3 = Dollar.Create directAmount
  7. d2 = d3
  8. Check.Quick ``create then times then add should be same as times then add then create``
  9. // Ok, passed 100 tests.

So this “different paths, same result” approach is very fruitful, and we can generate lots of paths this way.

Dollar properties — version 4

Shall we call it done then? I would say not!

We are beginning to get a whiff of a code smell. All this (start * multiplier) + adder code seems like a bit of duplicated logic, and could end up being brittle.

Can we abstract out some commonality that is present all these cases?

If we think about it, our logic is really just this:

  • Transform the amount on the “inside” in some way.
  • Transform the amount on the “outside” in the same way.
  • Make sure that the results are the same.

But to test this, the Dollar class is going to have to support an arbitrary transform! Let’s call it Map!

Now all our tests can be reduced to this one property:

Dollar map

Let’s add a Map method to Dollar. And we can also rewrite Times and Add in terms of Map:

  1. type Dollar = {amount:int }
  2. with
  3. member this.Map f =
  4. {amount = f this.amount}
  5. member this.Times multiplier =
  6. this.Map (fun a -> a * multiplier)
  7. member this.Add adder =
  8. this.Map (fun a -> a + adder)
  9. static member Create amount =
  10. {amount=amount}

Now the code for our property looks like this:

  1. let ``create then map should be same as map then create`` start f =
  2. let d0 = Dollar.Create start
  3. let d1 = d0.Map f
  4. let d2 = Dollar.Create (f start)
  5. d1 = d2

But how can we test it now? What functions should we pass in?

Don’t worry! FsCheck has you covered! In cases like this, FsCheck will actually generate random functions for you too!

Try it — it just works!

  1. Check.Quick ``create then map should be same as map then create``
  2. // Ok, passed 100 tests.

Our new “map” property is much more general than the original property using “times”, so we can eliminate the latter safely.

Logging the function parameter

There’s a little problem with the property as it stands. If you want to see what the function is that FsCheck is generating, then Verbose mode is not helpful.

  1. Check.Verbose ``create then map should be same as map then create``

Gives the output:

  1. 0:
  2. 18
  3. <fun:Invoke@3000>
  4. 1:
  5. 7
  6. <fun:Invoke@3000>
  7. -- etc
  8. 98:
  9. 47
  10. <fun:Invoke@3000>
  11. 99:
  12. 36
  13. <fun:Invoke@3000>
  14. Ok, passed 100 tests.

We can’t tell what the function values actually were.

However, you can tell FsCheck to show more useful information by wrapping your function in a special F case, like this:

  1. let ``create then map should be same as map then create2`` start (F (_,f)) =
  2. let d0 = Dollar.Create start
  3. let d1 = d0.Map f
  4. let d2 = Dollar.Create (f start)
  5. d1 = d2

And now when you use Verbose mode…

  1. Check.Verbose ``create then map should be same as map then create2``

… you get a detailed log of each function that was used:

  1. 0:
  2. 0
  3. { 0->1 }
  4. 1:
  5. 0
  6. { 0->0 }
  7. 2:
  8. 2
  9. { 2->-2 }
  10. -- etc
  11. 98:
  12. -5
  13. { -5->-52 }
  14. 99:
  15. 10
  16. { 10->28 }
  17. Ok, passed 100 tests.

Each { 2->-2 }, { 10->28 }, etc., represents the function that was used for that iteration.

TDD vs. property-based testing

How does property-based testing (PBT) fit in with TDD? This is a common question, so let me quickly give you my take on it.

First off, TDD works with specific examples, while PBT works with universal properties.

As I said in the previous post, I think examples are useful as a way into a design, and can be a form of documentation.
But in my opinion, relying only on example-based tests would be a mistake.

Property-based approaches have a number of advantages over example-based tests:

  • Property-based tests are more general, and thus are less brittle.
  • Property-based tests provide a better and more concise description of requirements than a bunch of examples.
  • As a consequence, one property-based test can replace many, many, example-based tests.
  • By generating random input, property-based tests often reveal issues that you have overlooked, such as dealing with nulls, missing data, divide by zero, negative numbers, etc.
  • Property-based tests force you to think.
  • Property-based tests force you to have a clean design.

These last two points are the most important for me. Programming is not a matter of writing lines of code, it is about creating a design that meets the requirements.

So, anything that helps you think deeply about the requirements and what can go wrong should be a key tool in your personal toolbox!

For example, in the Roman Numeral section, we saw that accepting int was a bad idea (the code broke!). We had a quick fix, but really we should model
the concept of a PositiveInteger in our domain, and then change our code to use that type rather than just an int.
This demonstrates how using PBT can actually improve your domain model, not just find bugs.

Similarly, introducing a Map method in the Dollar scenario not only made testing easier, but actually improved the usefulness of the Dollar “api”.

Stepping back to look at the big picture, though, TDD and property-based testing are not at all in conflict. They share the same goal of building correct programs,
and both are really more about design than coding (think “Test-driven design“ rather than “Test-driven development“).

The end, at last

So that brings us to the end of another long post on property-based testing!

I hope that you now have some useful approaches that you can take away and apply to your own code base.

Next time, we’ll look at some real-world examples, and how you can create custom generators that match your domain.

The code samples used in this post are available on GitHub.