Unit testing not as straightforward as expected

I needed some quick unit testing for a library, to rule out a run-time error. In the end, it wasn’t that complicated, but the “straightforward” way of implmenting unit testing with haskell/cabal isn’t as straightforward as it could be.

I ended up implementing this guy’s solution (stackoverflow.com, 8 years ago):

… which is a combination of

test-framework, HUnit, test-framework-hunit

with cabal Test Suite exitcode-stdio-1.0.

It does exactly what I needed, only the following things felt a bit awkard:

  • What is this alternative cabal test suite detailed-0.9? Documentation is slim. When should I use one over the other?
  • Those three libraries are fine, I guess, but given the basic nature of the task: Why isn’t it just one thing? I tried working with HUnit alone, BUT exitcode-stdio-1.0 requires working with exit codes that I have to put in place manually … thus I prefer the defaultMain from test-framework.

I certainly don’t mean to complain. Maybe there is a more straightforward way of implementing unit testing that I overlooked.

If, however, this is how it’s done, I hope this posts contributes to the documentation of the topic.

2 Likes

Nowadays hspec is a popular testing framework, with nice introductory material.

It borrows from rspec and seems for sure a better experience than what suggested in that stackoverflow answer. If hspec if fit for your purposes, I suggest giving it a go!

5 Likes

I believe exitcode-stdio is the standard way to test and the other ways are not really used anymore.

I didn’t run into issues with using HUnit directly with this code:

module Main where

import Test.HUnit

foo :: Int -> (Int, Int)
foo 3 = (1, 2)
foo _ = (0, 0)

partA :: Int -> IO (Int, Int)
partA _ = pure (5, 2) -- change to (5,3) if you want the tests to succeed

partB :: Int -> IO Bool
partB x = pure (x == 3) 

test1, test2 :: Test
test1 = TestCase $ assertEqual "for (foo 3)," (1, 2) (foo 3)
test2 = TestCase $ do
  (x, y) <- partA 3
  assertEqual "for the first result of partA," 5 x
  b <- partB y
  assertBool ("(partB " ++ show y ++ ") failed") b

main :: IO ()
main = runTestTTAndExit (TestList [TestLabel "test1" test1, TestLabel "test2" test2])
1 Like

nice, the runTestTTAndExit is all I need for straightforwardness

CS SYD - Blog has a few posts on testing in general and with haskell, you might find those useful. I’d also recommend at least reviewing what GitHub - NorfairKing/sydtest: A modern testing framework for Haskell with good defaults and advanced testing features. can do and if that would be useful for your projects.

2 Likes