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.
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
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
- 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
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
- 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
Property #6: lcm(a,b) times gcd(a,b) must be equal to a times 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
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 ;)