Find the best properties for Property Based Testing

Nicolas Dubien
May 7, 2018 · 3 min read

Tips to build properties — Examples written using fast-check

Property based testing became famous thanks to QuickCheck in Haskell. It is another way to test your code which is fully complementary to classical unit-test methods.

It tries to discover inputs causing a property to be falsy by testing it against multiple — in general a hundred is enough — eligible inputs. A property can be seen as:

for any (x, y, …) such as precondition(x, y, …) holds property(x, y, …) is true

You may refer to https://medium.com/@nicolasdubien/introduction-to-property-based-testing-f5236229d237 for more details about property based testing.

Let’s find our properties

The main difficulty when moving to property based testing is: finding the right properties, for a given algorithm. Most of the time we tend to test the code against… itself which is not the right way to do.

This article describes several kinds of properties that you should think about when trying to find yours. It provides examples written using fast-check, a property based testing framework for JavaScript and TypeScript. Everything can easily be transposed to other frameworks.

Basically fast-check itself uses properties to test itself, so you might be able to run properties on your own code too.

The examples below are all available at http://runkit.com/dubzzz/property-based-find-the-best-properties. Try to run them locally and play with them.

You may also clone the repository https://github.com/dubzzz/fast-check-examples to have other examples — and maybe add some.

Characteristics independent of the inputs

When? Some characteristics of the output are independent of its inputs. If those characteristics are strong enough they can be defined as properties. Being fully agnostic of the inputs is most of the time too restrictive to have a useful property but can constitute a first try.

Here are some examples of such properties:

  • for any floating point number d, Math.floor(d) is an integer
  • for any integer n, Math.abs(n) ≥ 0

Characteristics derived from the inputs

When? The output of your algorithm is easy to check: it has a relationship with the input. The relationship might just be a set of traits you expect to observe in the output based on the input.

Property #1: The average of a and b must be between a and b

for any a and b integers the average of a and b is between a and b

average: compute the average of two numbers a and b

Property #2: The decomposition in prime numbers of n must be such that the product of all numbers in the decomposition equals n

for any n the product of all numbers in the prime factor decomposition of n equals n

decompose: prime factors decomposition of n

Other examples:

  • for any array — data, sorted(data) and data contains the same elements
  • for any n1, n2 integers such that n1 != n2, romanString(n1) != romanString(n2)
  • for any floating point number d, Math.floor(d) is an integer such as d-1 ≤ Math.floor(d) ≤ d

Restricted set of inputs with useful characteristics

When? Some inputs have a very simple output. In some cases, you might be able to extract some very specific inputs for which you can easily compute the output.

Property #3: removing duplicates of an array of unique values returns the array itself

for any array data with no duplicates — called data the result of removing duplicates from data is data itself

removeDuplicates: remove duplicates from data — do not change data

Property #4: the concatenation of a, b and c always contains string b

for any a, b and c strings the concatenation of a, b and c always contains b

contains: check if argument[0] is a substring of argument[1]

Other examples:

  • for any prime number p, its decomposition into prime factors is itself

Characteristics on combination of functions

When? Two or more functions can be combined to compute something easy to check.

Property #5: zipping then unzipping a file should result in the original file

zip: compress — unzip: uncompress

Property #6: lcm(a,b) times gcd(a,b) must be equal to a times b

lcm: least common multiple of a and b — gcd: greatest common divisor of a and b

Comparison with a simpler implementation

When? A simpler implementation of the algorithm exists.

Property #7: c is contained inside sorted array data for binary search is equivalent to c is contained inside data for linear search

binaryContains: binary search — linearContains: linear search, takes elements one by one

More examples…

You can check for additional examples at: https://github.com/dubzzz/fast-check-examples and more are coming…

Please leave a clap or a comment if you liked this article ;)