f :: [a] -> [a] -- we should think of any such function f = reverse -- that was our pick g :: Int -> String g x = show x ++ show x ++ show x
A Simple QuickCheck Property
Frege comes with a testing facility that enables property-based testing.
Let’s have a first glance on what we can do with it.
In the post "A magical trick" we worked with these functions f and g.
We found that we can freely choose between
map g (f [1, 2, 3])
f (map g [1, 2, 3])
to arrive at the same result.
In other words, we found a property (I prefer the term invariant) that we can test.
Here is how we can define a property in Frege with QuickCheck:
import frege.test.QuickCheck commutativity xs = property ( map g (f xs) == f (map g xs) )
Having defined this property, we can quickly check whether it holds:
Which yields us
+++ OK, passed 100 tests.
That is seriously surprising! How could QuickCheck possibly know what test data
to generate for the 100 tests? We did not even tell it the argument type!
The clever Frege type inference comes into play here.
To see the inferred type of commutativity we can ask the REPL:
:t commutativity [Int] -> Bool
Let’s follow through what the Frege type inference engine must have considered:
The function g takes an Int argument
and the argument to
map g must be a list of the arguments to g, therefore,
xs can only be a list of Int values. Furthermore, f works on a list of any types and thus
imposes no further restrictions.
There is some reasonably advanced thinking involved to arrive at the same conclusion that Frege infers at compile time.
Knowing that xs is of type [Int], quickCheck can ask the list type to produce arbitrary values of itself (in various sizes), while lists in turn ask Int to also produce arbitrary Int values. This works in a type-safe manner since both List and Int are instances of the Arbitrary typeclass: they belong to the class of types that know how to generate arbitrary values of their type.
We haven’t talked about the function property, yet. What is that for? Well, while the test above runs perfectly well without it, there is value in wrapping the boolean expression in a Property type. One benefit is that the QuickCheck tool can scan for all functions of that type and run them all at once without resorting to any conventions like "must start with prop_" or so.
We claimed that we can choose any functions f and g that satisfy our types. Try a few and do your very best to break the invariant! Honestly, do it! You will learn something.
Hint: When you try with infinite lists or functions g that have a side-effect, you will run into issues with checking for equality. Reading the paper below may help, then.
There is so much more to say about QuickCheck. We will do so in future posts.
|Koen Claessen, John Hughes||
QuickCheck: A Lightweight Tool for Random Testing of Haskell Programs http://www.eecs.northwestern.edu/~robby/courses/395-495-2009-fall/quick.pdf