Skip to content

Latest commit

 

History

History
120 lines (101 loc) · 4.39 KB

README.md

File metadata and controls

120 lines (101 loc) · 4.39 KB

Build Status tasty-discover-nightly tasty-discover-lts Hackage Status

tasty-hedgehog

This provides tasty integration for hedgehog.

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 hedgehog:

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"
        prop_reverse_involutive
    ]

We then add this as a test suite in our .cabal file:

test-suite tasty-hedgehog-tests
  type:                exitcode-stdio-1.0
  main-is:             Main.hs
  hs-source-dirs:      test
  build-depends:       base                   >= 4.8  && < 4.18
                     , tasty                  >= 0.11 && < 1.5
                     , tasty-expected-failure >= 0.11 && < 0.13
                     , hedgehog               >= 1.2  && < 1.3
                     , tasty-hedgehog         >= 1.4  && < 1.5
  ghc-options:         -Wall
  default-language:    Haskell2010

and we should be good to go.

Running the tests will give you something like this:

success example

We're already leaning on parametricity in our test of reverse. 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"
        prop_reverse_involutive
    , expectFail $ testProperty
        "badReverse involutive fails"
        "prop_badReverse_involutive"
        prop_badReverse_involutive
    ]

and now running the tests will give you something like this:

success and failure example