Skip to content

Latest commit

 

History

History
166 lines (120 loc) · 5.65 KB

tutorial-property-creation.md

File metadata and controls

166 lines (120 loc) · 5.65 KB

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).