Testing And Benchmarking in Haskell

Here’s the translation of the Go testing and benchmarking example to Haskell:

module Main where

import Test.HUnit
import Criterion.Main

-- We'll be testing this simple implementation of an
-- integer minimum. Typically, the code we're testing
-- would be in a source file named something like
-- IntUtils.hs, and the test file for it would then
-- be named IntUtilsTest.hs.

intMin :: Int -> Int -> Int
intMin a b = if a < b then a else b

-- A test is created by writing a function with a name
-- beginning with 'test'.

testIntMinBasic :: Test
testIntMinBasic = TestCase $ do
    let ans = intMin 2 (-2)
    assertEqual "IntMin(2, -2)" (-2) ans

-- Writing tests can be repetitive, so it's idiomatic to
-- use a table-driven style, where test inputs and
-- expected outputs are listed in a table and a single loop
-- walks over them and performs the test logic.

testIntMinTableDriven :: Test
testIntMinTableDriven = TestList
    [ TestCase $ assertEqual ("IntMin " ++ show a ++ " " ++ show b) want (intMin a b)
    | (a, b, want) <- [ (0, 1, 0)
                      , (1, 0, 0)
                      , (2, -2, -2)
                      , (0, -1, -1)
                      , (-1, 0, -1)
                      ]
    ]

-- Benchmark tests in Haskell are typically created using
-- the Criterion library. We define a benchmarking function
-- that will be executed multiple times to get precise measurements.

benchmarkIntMin :: Benchmark
benchmarkIntMin = bench "intMin" $ whnf (intMin 1) 2

-- Main function to run tests and benchmarks

main :: IO ()
main = do
    -- Run the tests
    counts <- runTestTT $ TestList [testIntMinBasic, testIntMinTableDriven]
    print counts
    
    -- Run the benchmarks
    defaultMain [benchmarkIntMin]

To run the tests and benchmarks, you would typically use a build tool like Stack or Cabal. Here’s an example of how you might run them:

$ stack test
== RUN TestIntMinBasic
--- PASS: TestIntMinBasic
=== RUN TestIntMinTableDriven
--- PASS: TestIntMinTableDriven
PASS
Cases: 6  Tried: 6  Errors: 0  Failures: 0

$ stack bench
benchmarking intMin
time                 2.010 ns   (1.998 ns .. 2.024 ns)
                     0.999 R²   (0.999 R² .. 1.000 R²)
mean                 2.010 ns   (2.001 ns .. 2.022 ns)
std dev              34.97 ps   (27.30 ps .. 46.90 ps)

In Haskell, we use the HUnit library for unit testing and the Criterion library for benchmarking. The structure is similar to Go’s testing, but with some differences:

  1. Tests are typically grouped into a TestList.
  2. We use assertEqual instead of custom error reporting.
  3. Benchmarks are defined using the bench function from Criterion.
  4. The whnf function is used to ensure proper evaluation in benchmarks.

The table-driven test style is maintained, but adapted to Haskell’s list comprehension syntax. The benchmark is set up to run the intMin function multiple times, similar to the Go version.

Remember to add hunit and criterion to your project’s dependencies to use these testing and benchmarking capabilities.