fitspec-0.3.1: doc/tutorial-property-creation.md
Using FitSpec to guide property creation
----------------------------------------
Suppose we want to write test properties for the function `sort`,
but we do not know where to start.
We can use FitSpec to guide property creation.
We first import what is needed:
import Test.FitSpec
import Data.List (sort)
Then we need a property list function: given a sorting implementation, return
the properties applied to *that* implementation. Since we don't have any
properties, we will start by returning and empty list:
properties :: (Show a, Ord a, Listable a)
=> ([a] -> [a]) -> [Properties]
properties sort' =
[]
Then, we need a main function, that calls the FitSpec's `report` function,
which will report the results of mutation testing.
It needs a function to be mutated and the property list.
main = report (sort::[Int]->[Int]) properties
Optionally, for a nicer output, you might want to use the reportWith function,
which allows specifying function and argument names (among other options):
main = reportWith args { callNames = ["sort xs"] }
(sort::[Int]->[Int]) properties
By having the three sections above in a file called sorting.hs,
we then compile and run:
$ ghc -ipath/to/leancheck:path/to/fitspec sorting.hs
[9 of 9] Compiling Main ( sorting.hs, sorting.o )
Linking sorting ...
$ ./sorting
Results based on at most 4000 test cases for each of 2000 mutant variations.
Property #Survivors Smallest or simplest
sets (%Killed) surviving mutant
[] 2000 (0%) \xs -> case xs of
[] -> [0]
_ -> sort xs
The output is self-explanatory. Obviously, our empty property set `[]` did not
kill any mutant (`0%`). In other words, all of the `2000` mutants survived.
(The actual number of mutants tested will vary depending on your machine, it
will probably be higher than 2000 *in this case*, by default FitSpec runs for
at least 5 seconds.)
The surviving mutant shown on the third column is clearly not a valid
implementation of sort. For the empty list, it returns `[0]`. We should
improve our property set by killing that mutant. Lets start very simple by
adding a property stating that sorting an empty list must yield an empty list:
properties sort' =
[ property $ sort' [] == []
]
Above, we need to apply the function `property` to each property in the list.
Now:
$ ./sorting
Results based on at most 4000 test cases for each of 2000 mutant variations.
Property #Survivors Smallest or simplest
sets (%Killed) surviving mutant
[1] 984 (49%) \xs -> case xs of
[0] -> []
_ -> sort xs
[] 2000 (0%) \xs -> case xs of
[] -> [0]
_ -> sort xs
The last row of results is the same as before (all mutants still obviously
survive the empty property set). The *first row* show that there are `984`
*surviving mutants* (`49%`) for the first property `[1]`: the smallest one is
shown on the third column. It sorts `[0]` to `[]`, which is not valid. Lets
still be very simple -- sorting a list with one value must yield a list with
the same value:
properties sort' =
[ property $ sort' [] == []
, property $ \x -> sort' [x] == [x]
]
Note that, our new property (2) has a free variable. Now:
$ ./sorting
Results based on at most 1000 test cases for each of 500 mutant variations.
Property #Survivors Smallest or simplest
sets (%Killed) surviving mutant
[1,2] 134 (73%) \xs -> case xs of
[0,0] -> []
_ -> sort xs
...
Only 27% of mutants to go, perhaps a property stating that the length of the
sorted list should not change?
properties sort' =
[ property $ sort' [] == []
, property $ \x -> sort' [x] == [x]
, property $ \xs -> length (sort' xs) == length xs
]
Now:
$ ./sorting
Results based on at most 1000 test cases for each of 500 mutant variations.
Property #Survivors Smallest or simplest
sets (%Killed) surviving mutant
[2,3] 12 (97%) \xs -> case xs of
[0,0] -> [0,1]
_ -> sort xs
...
Conjectures based on at most 1000 test cases for each of 500 mutant variations:
[3] ==> [1] 95% killed (likely)
The first row show that the current candidate minimal-complete propety-set
kills all but `4` mutants and is composed only by properties 2 and 3 (`[2,3]`).
When possible, FitSpec also reports *conjectures* based on test results. In
this case, that property `sort [] == []` (1) follows from the length property
(3). Since that is *clearly* true, we can safely remove that property.
properties sort' =
[ property $ \x -> sort' [x] == [x]
, property $ \xs -> length (sort' xs) == length xs
, property $ \x xs -> elem x (sort' xs) == elem x xs
]
Now:
$ ./sorting
Property #Survivors Smallest or simplest
sets (%Killed) surviving mutant
[2,3] 2 (99%) \xs -> case xs of
[0,1] -> [1,0]
_ -> sort xs
...
Conjectures based on at most 1000 test cases for each of 500 mutant variations:
[2,3] ==> [1] 99% killed (possible+)
We could go on, but *at this point, you probably got how it works*. As an
exercise you can try to improve our property-set over `sort` by killing the
above mutant by adding a new property. Later, you can try to improve the
results by increasing the time limit (`minimumTime = 10` on args).