As with most languages nowadays, we want to have some unit testing libraries/frameworks. Obviously Haskell is no different and we have tools such as HUnit.
Let’s first create a simple little bit of code to test, here’s my Calculator.hs file (abridged just to show the code I intend to write tests for)
module Modules.Calculator where factorial :: (Integral a) => a -> a factorial 0 = 1 factorial n = n * factorial (n - 1)
We’ll need to have installed the HUnit package, if you haven’t already then run
cabal install --lib QuickCheck HUnit
We’ll store our tests in a folder named test off of our root folder (the name of the folder can be altered if you prefer).
Here’s our test code (my file is named CalculatorTests.hs)
module Main (main) where import Test.HUnit import System.Exit import Modules.Calculator as Calc testZeroCase = TestCase(assertEqual "Factorial 1" (1) (Calc.factorial 1)) testNonZeroCase = TestCase(assertEqual "Factorial 10" (2) (Calc.factorial 10)) main :: IO () main = do counts <- runTestTT ( test [ testZeroCase, testNonZeroCase ]) if (errors counts + failures counts == 0) then exitSuccess else exitFailure
In the above code we import our library code and have two tests, the first tests our factorial code with a 0 value, the second tests a non-zero. In this instance I’ve purposefully written a breaking test.
Before we run these and/or fix the tests let’s see what we’re doing with the test code. We create a function for the TestCase and within the TestCase we have our assertion along with a message prefix (the string), next we have the expected value and finally the actual value or in this case the function call which returns the actual value.
In main we basically create the test runner and supply an array of the test functions to be run, finally we exit the runner with success or failure.
Now before we can run our tests we need to declare a Test-Suite within the .cabal file. We add the following to the file
Test-Suite test-Calculator type: exitcode-stdio-1.0 hs-source-dirs: test . default-language: Haskell2010 main-is: CalculatorTests.hs other-modules: Modules.Calculator build-depends: base >=4.14 && <4.15, HUnit
This tells cabal the folder to look for the tests (test in this case) along with . for the current folder (otherwise our tests won’t find out Calculator module). We then state what the main application for the cabal to run for the tests, include other modules etc.
Now run
cabal test
If all goes well you’ll get something like the following
Running 1 test suites... Test suite test-Calculator: RUNNING... ### Failure in: 1 test\CalculatorTests.hs:9 Factorial 10 expected: 2 but got: 3628800 Cases: 2 Tried: 2 Errors: 0 Failures: 1 Test suite test-Calculator: FAIL Test suite logged to: D:\Development\somefolder\test\HaskellBasics-0.1.0.0-test-HaskellBasics.log 0 of 1 test suites (0 of 1 test cases) passed. cabal.exe: Tests failed for test:test-Calculator from Calculator-0.1.0.0.
Obviously the test with the prefix message “Factorial 10” failed with the expected value 2 but the actual value 3628800, so we can fix that test case so it looks like this
testNonZeroCase = TestCase(assertEqual "Factorial 10" (3628800) (Calc.factorial 10))
and now all our tests will pass.
I’ll leave it to the reader to look into the other assertions etc. For example, checkout Test.HUnit.Base
Other References