A guide to writing property-based tests. The author, John Hughes, co-wrote QuickCheck in Haskell, the first package for property-based testing.
Published conference paper: http://dx.doi.org/10.1007/978-3-030-47147-7_4
Some quotes:
"Avoid replicating your code in your tests," because it's lot of work and likely to contain the same errors as your code.
"Test your tests," because if your generator and shrinker produce invalid values, everything else will fail too.
Validity testing
Validity testing consists of defining a function to check the invariants of your datatypes, writing properties to test that your generators and shrinkers only produce valid results, and writing a property for each function under test that performs a single random call, and checks that the return value is valid.
Postcondition testing
A postcondition tests a single function, calling it with random arguments, and checking an expected relationship between its arguments and its result.
Metamorphic testing
A metamorphic property tests a single function by making (usually) two related calls, and checking the expected relationship between the two results.
Inductive testing
Inductive properties relate a call of the function-under-test to calls with smaller arguments. A set of inductive properties covering all possible cases together test the base case(s) and induction step(s) of an inductive proof-of-correctness. If all the properties hold, then we know the function is correct–inductive properties together make up a complete test.
Model-based testing
A model-based property tests a single function by making a single call, and comparing its result to the result of a related “abstract operation” applied to related abstract arguments. An abstraction functions maps the real, concrete arguments and results to abstract values, which we also call the “model”.