Testing

46.1 Hspec

-- test/Spec.hs
import Test.Hspec
import Test.QuickCheck
import Control.Exception (evaluate)
import MyLib (quicksort, safeDiv)

main :: IO ()
main = hspec $ do
  describe "quicksort" $ do
    it "sorts a simple list" $
      quicksort [3, 1, 2] `shouldBe` [1, 2, 3 :: Int]
    it "handles the empty list" $
      quicksort ([] :: [Int]) `shouldBe` []
    it "is idempotent" $ property $
      \xs -> quicksort (quicksort xs) == quicksort (xs :: [Int])
    it "preserves length" $ property $
      \xs -> length (quicksort xs) == length (xs :: [Int])

  describe "safeDiv" $ do
    it "returns Nothing for division by zero" $
      safeDiv 1 0 `shouldBe` Nothing
    it "errors on head []" $
      evaluate (head ([] :: [Int])) `shouldThrow` anyException

46.2 QuickCheck: Property-Based Testing

Shrinking finds minimal failing cases automatically. Test type class laws with property tests (e.g., quickcheck-classes, hedgehog-classes).

Alternatives: Hedgehog (integrated shrinking, better generators), falsify, smallcheck (exhaustive small inputs).

import Test.QuickCheck
import Data.List (sort)

prop_reverseReverse :: [Int] -> Bool
prop_reverseReverse xs = reverse (reverse xs) == xs

prop_sortOrdered :: [Int] -> Bool
prop_sortOrdered xs = ordered (sort xs)
  where ordered ys = and (zipWith (<=) ys (drop 1 ys))

prop_insertLength :: Int -> [Int] -> Property
prop_insertLength x xs = classify (null xs) "empty" $ length (x : xs) === length xs + 1

-- Custom generators
data Color = Red | Green | Blue deriving (Show, Eq, Enum, Bounded)
instance Arbitrary Color where
  arbitrary = elements [minBound .. maxBound]

newtype Positive' = Positive' Int deriving Show
instance Arbitrary Positive' where
  arbitrary = Positive' <$> choose (1, 1000)
  shrink (Positive' n) = [Positive' m | m <- shrink n, m > 0]

-- Preconditions
prop_divMod :: Int -> Int -> Property
prop_divMod x y = y /= 0 ==> (x `div` y) * y + (x `mod` y) == x

main :: IO ()
main = do
  quickCheck prop_reverseReverse
  quickCheck prop_sortOrdered
  quickCheck (withMaxSuccess 1000 prop_divMod)
  verboseCheck prop_insertLength

46.3 Tasty and Other Tools

doctest (run examples in Haddock comments), tasty-golden (golden files), hspec-wai (web apps), HUnit (unit tests), hpc coverage (cabal test --enable-coverage).

import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck as QC

main :: IO ()
main = defaultMain $ testGroup "All tests"
  [ testCase "addition" $ 2 + 2 @?= (4 :: Int)
  , QC.testProperty "reverse involution" (\xs -> reverse (reverse xs) == (xs :: [Int]))
  ]