Using Haskell's QuickCheck for Python
Writing proposals? Try Proppy, our new product!
Everyone I know who has ever used QuickCheck in anger cannot live without it.
The basic idea behind QuickCheck is that we write a proposition, e.g.: Reversing a list twice returns the same as the original list. In code:
reverse(reverse(x)) == x
QuickCheck will try to find a counterexample to that proposition by
generating random values for x. And it is very good at finding mean values:
Haskell Quickcheck enters a bar: asks for 1 beer, 42 beers, -Inifinity beers, shaves bartenders beard, sets off a tactical nuke.
— Michael Neale (@michaelneale) February 17, 2015
Shrinking
The really cool part is that when QuickCheck finds a counterexample it will attempt to make the example smaller. Lets say we have a broken reverse function that sorts the list instead. So for the input:
[2, 0, -1, 3, 9, 12]
our broken reverse returns:
[-1, 0, 2, 3, 9, 12]
instead of the correct:
[12, 9, 3, -1, 0, 2]
QuickCheck will shrink the input to [2, 0, -1] which still breaks
but is much easier to debug. Neat!
Calling Python from Haskell
There is a relatively recent package on Hackage called pyfi. It calls Python through it's C FFI and passes values by converting them to and from JSON. It comes with a great tutorial on its github page.
QuickCheck
We start with a simple python module:
# moremath.py
def square(x):
return x * x
In Haskell every call to Python happens in the IO monad so we have to use monadic QuickCheck:
-- squarecheck.hs
import Python
import Test.QuickCheck
import Test.QuickCheck.Monadic
square :: Int -> IO Int
square = defVV "from moremath import square as export"
main = do
quickCheck $ monadicIO $ do
v <- pick arbitrary
r <- run $ square v
assert $ r == v * v
Now we can run the tests like so:
$ runhaskell squarecheck.hs
+++ OK, passed 100 tests.
That's all there is to it!
Edit: Someone sent me a link to hypothesis which looks like a really solid Python version of QuickCheck. The API has been adjusted to fit into Python. Most importantly, it has a good shrinker!
