An example of usage
We're going to need some toy examples to show how this is meant to work.
(If you want to look at the whole file that this example comes from, which includes all of the necessary imports, it is here)
We're going to use the simplest example of a property that we can find: if you reverse a list, and then reverse it again, you end up with the list that you started with.
If were to use the formal term for that, we would say that the
reverse function is involutive.
To test this, we'll need a random list generated by
genAlphaList :: Gen String genAlphaList = Gen.list (Range.linear 0 100) Gen.alpha
We'll also need something to test that a function is involutive, which might sound scarier than the implementation:
test_involutive :: (MonadTest m, Eq a, Show a) => (a -> a) -> a -> m () test_involutive f x = f (f x) === x
Thus armed, we write a property to test that the reverse function is actually involutive:
prop_reverse_involutive :: Property prop_reverse_involutive = property $ do xs <- forAll genAlphaList -- hedgehog-1.0 introduced a classification feature -- it's optional, but we use it here for fun :) classify "empty" $ length xs == 0 classify "small" $ length xs < 10 classify "large" $ length xs >= 10 test_involutive reverse xs
(We're only testing with lists of
Char, but the type signature of
reverse lets us know that the type of the elements of the list can't effect how the function works, via parametricity)
We can now use
tasty to run the
hedgehog tests for that property in a test executable:
main :: IO () main = defaultMain $ testGroup "tasty-hedgehog tests" [ testProperty "reverse involutive" prop_reverse_involutive ]
We then add this as a test suite in our
test-suite tasty-hedgehog-tests type: exitcode-stdio-1.0 main-is: Main.hs hs-source-dirs: test build-depends: base >= 4.8 && < 4.11 , tasty >= 0.11 && < 0.12 , tasty-expected-failure >= 0.11 && < 0.12 , hedgehog >= 0.5 && < 0.6 , tasty-hedgehog >= 0.1 && < 0.2 ghc-options: -Wall default-language: Haskell2010
and we should be good to go.
Running the tests will give you something like this:
We're already leaning on parametricity in our test of
Maybe a free theorem pops out of the type of
reverse that guarantees that anything with that type signature is involutive automatically.
Because we're too lazy to check that ourselves, we'll come up with a counter-example:
badReverse :: [a] -> [a] badReverse  =  badReverse [_] =  badReverse as = reverse as
and test it with a property:
prop_badReverse_involutive :: Property prop_badReverse_involutive = property $ do xs <- forAll genAlphaList test_involutive badReverse xs
We can plug this into our test-suite - remembering to mark the test as an expected failure -
main :: IO () main = defaultMain $ testGroup "tasty-hedgehog tests" [ testProperty "reverse involutive" prop_reverse_involutive , expectFail $ testProperty "badReverse involutive fails" prop_badReverse_involutive ]
and now running the tests will give you something like this: