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:
- Tests are typically grouped into a
TestList
. - We use
assertEqual
instead of custom error reporting. - Benchmarks are defined using the
bench
function from Criterion. - 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.