Haskell Testing Guide — HUnit, QuickCheck, and Test-Driven Development
In this tutorial, you will learn about Haskell Testing Guide. We cover key concepts, practical examples, and best practices to help you master this topic.
Haskell testing uses HUnit for traditional unit assertions, QuickCheck for property-based random testing with automatic shrinking, and tasty/HSpec for test suite organization -- all runnable with cabal test.
HUnit
import Test.HUnit
-- Test cases
testReverse :: Test
testReverse = TestCase $ do
assertEqual "reverse [1,2,3]" [3,2,1] (reverse [1,2,3])
assertEqual "reverse []" ([] :: [Int]) (reverse [])
assertBool "reverse preserves length" $
length (reverse [1..10]) == 10
-- Group tests
tests = TestList [
TestLabel "reverse" testReverse,
TestLabel "other" testOther
]
-- Run
main :: IO ()
main = runTestTT tests >> return ()
QuickCheck
import Test.QuickCheck
-- Property: reverse twice is identity
prop_reverseReverse :: [Int] -> Bool
prop_reverseReverse xs = reverse (reverse xs) == xs
-- Property: length after reverse
prop_reverseLength :: [Int] -> Bool
prop_reverseLength xs = length (reverse xs) == length xs
-- Run tests
main = do
quickCheck prop_reverseReverse
quickCheck prop_reverseLength
Custom Generators
import Test.QuickCheck
-- Custom data generator
data Color = Red | Green | Blue deriving (Show, Enum)
instance Arbitrary Color where
arbitrary = elements [Red, Green, Blue]
shrink Red = []
shrink Green = [Red]
shrink Blue = [Green, Red]
-- Conditional property
prop_someColors :: Color -> Bool
prop_someColors c = case c of
Red -> True
Green -> True
Blue -> True
Tasty Test Framework
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
main = defaultMain $ testGroup "All Tests" [
testGroup "Unit" [
testCase "Reverse works" $
reverse [1,2,3] @?= [3,2,1]
],
testGroup "Properties" [
testProperty "reverse reverse" prop_reverseReverse
]
]
Common Mistakes
1. Not testing edge cases
Empty lists, negative numbers, large inputs. QuickCheck helps but doesn't guarantee coverage.
2. Writing non-terminating properties
prop_sorted xs = sort xs == sort xs is always true. Test actual properties, not tautologies.
3. Not shrinking
QuickCheck shrinks failing cases to minimal examples. Ensure your types have shrink instances for useful error messages.
Practice Questions
1. What is the difference between HUnit and QuickCheck? HUnit tests specific cases. QuickCheck generates random inputs and tests properties that should hold for all.
2. How do you run tests with cabal?
cabal test runs the test suite defined in the .cabal file. cabal test --show-details=direct for verbose output.
3. What does quickCheck return?
It prints results to stdout. Use quickCheckResult to get a programmatic Result value.
FAQ
{{< faq question="What is property-based testing?" >}} Instead of specific input-output pairs, define a property that should hold for all inputs and let the framework generate random test cases. {{< /faq >}}
{{< faq question="Can I use QuickCheck with IO?" >}}
Yes. Use quickCheck prop for pure properties. For IO properties, use Test.QuickCheck.Monadic with monadicIO.
{{< /faq >}}
{{< faq question="What is coverage in QuickCheck?" >}}
QuickCheck tracks how many cases pass each condition. Use cover, label, and collect to measure coverage.
{{< /faq >}}
What's Next
Now learn about parser combinators.
| Topic | Description | Link |
|---|---|---|
| Parsing | Parser combinators | {{< ref "20-parsing" >}} |
| Concurrency | Concurrent Haskell | {{< ref "21-concurrency" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro