Using Haskell’s QuickCheck for Python
Writing proposals? Try Proppy, our new product currently in beta !
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, 2020
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.
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 http://wearewizards.io/files/blog_subdomain/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!
- Share on Twitter
- Share on Facebook