We're planting a tree for every job application! Click here to learn more

Basic unit testing in Haskell using HUnit and Cabal

André Ferreira

13 Dec 2022

•

5 min read

Basic unit testing in Haskell using HUnit and Cabal
  • Haskell

Testing is an essential step in software development and if done early, it can avoid hours of stress in debugging the code. When I came to know Test-Driven Development (TDD), I became an adopter of this way of development and looked to apply it to my code. Another piece of knowledge that I became a fan of was the functional paradigm of programming, which has a more mathematical approach to code thinking.

Therefore, I invite you, reader, to a hike with me so I can show you how to apply TDD with functional programming by making a simple unit test using Haskell, with the HUnit library and Cabal (Common Architecture for Building Applications and Libraries).

Motivation

I wanted to apply TDD with Haskell. Although there were numerous sources where I could look, these sources addressed specific points in different styles of making unit tests with Haskell. With this post, I want to establish a basic starting point for those that wish to implement TDD in Haskell without much configuration.

Prerequisites

For this hike, we need to prepare ourselves. It is necessary to have GHC and Cabal installed on your computer. You can find the installer in the Haskell.org [download section] (Downloads (haskell.org)).

All ready! Let’s go!

The Cabal village

Let’s start our hike by creating a simple folder, named basic-sum and get inside it:

mkdir basic-sum && cd basic-sum

let’s use Cabal to create all the files for our test purposes:

cabal init 

the initial setting will be like this:

app/
  +- Main.hs
basic-sum.cabal
CHANGELOG.md

our file of interest will be the basic-sum.cabal, which is where we will configure the test suite for our application. Its initial look will be this:

cabal-version:      2.4
name:               basic-sum
version:            0.1.0.0
 
-- A short (one-line) description of the package.
-- synopsis:
 
-- A longer description of the package.
-- description:
 
-- A URL where users can report bugs.
-- bug-reports:
 
-- The license under which the package is released.
-- license:
 
-- The package author(s).
-- author:
 
-- An email address to which users can send suggestions, bug reports, and patches.
-- maintainer:
 
-- A copyright notice.
-- copyright:
-- category:
extra-source-files: CHANGELOG.md
 
executable basic-sum
    main-is:          Main.hs
 
    -- Modules included in this executable, other than Main.
    -- other-modules:
 
    -- LANGUAGE extensions used by modules in this package.
    -- other-extensions:
    build-depends:    base ^>=4.14.3.0
    hs-source-dirs:   app
    default-language: Haskell2010
 

So far, so good!

Time to camp

Let’s start our camp by mounting our test suite. First, create a directory called lib, which will be our library that will contain the functions that we want to test. Inside it, create a file named BasicSum.hs. The layout will be like this:

app/
  +- Main.hs
lib/
  +- BasicSum.hs
basic-sum.cabal
CHANGELOG.md

Now, we need to inform cabal of the existence of this library. In the basic-sum.cabal put the following section:

cabal-version:      2.4
name:               basic-sum
version:            0.1.0.0
 
-- A short (one-line) description of the package.
-- synopsis:
 
-- A longer description of the package.
-- description:
 
-- A URL where users can report bugs.
-- bug-reports:
 
-- The license under which the package is released.
-- license:
 
-- The package author(s).
-- author:
 
-- An email address to which users can send suggestions, bug reports, and patches.
-- maintainer:
 
-- A copyright notice.
-- copyright:
-- category:
extra-source-files: CHANGELOG.md
 
library basic-sum-lib
    exposed-modules: BasicSum
    hs-source-dirs: lib
    build-depends: base ^>=4.14
    default-language: Haskell2010
 
executable basic-sum
    main-is:          Main.hs
 
    -- Modules included in this executable, other than Main.
    -- other-modules:
 
    -- LANGUAGE extensions used by modules in this package.
    -- other-extensions:
    build-depends:    base ^>=4.14.3.0
    hs-source-dirs:   app
    default-language: Haskell2010
 

Now to create our test suite. Create a directory called tests and inside it create a file named BasicSumTest.hs. The layout should be like the following:

app/
  +- Main.hs
lib/
  +- BasicSum.hs
tests/
  +- BasicSumTest.hs
basic-sum.cabal
CHANGELOG.md

Now we add the following section to our basic-sum.cabal file:

cabal-version:      2.4
name:               basic-sum
version:            0.1.0.0
 
-- A short (one-line) description of the package.
-- synopsis:
 
-- A longer description of the package.
-- description:
 
-- A URL where users can report bugs.
-- bug-reports:
 
-- The license under which the package is released.
-- license:
 
-- The package author(s).
-- author:
 
-- An email address to which users can send suggestions, bug reports, and patches.
-- maintainer:
 
-- A copyright notice.
-- copyright:
-- category:
extra-source-files: CHANGELOG.md
 
library basic-sum-lib
    exposed-modules: BasicSum
    hs-source-dirs: lib
    build-depends: base ^>=4.14
    default-language: Haskell2010
 
executable basic-sum
    main-is:          Main.hs
 
    -- Modules included in this executable, other than Main.
    -- other-modules:
 
    -- LANGUAGE extensions used by modules in this package.
    -- other-extensions:
    build-depends:    base ^>=4.14.3.0
    hs-source-dirs:   app
    default-language: Haskell2010
 
test-suite tests
    type: exitcode-stdio-1.0
    main-is: BasicSumTest.hs
    build-depends: base ^>=4.14, HUnit ^>=1.6, basic-sum-lib
    hs-source-dirs: tests
    default-language: Haskell2010
 

Following the trail of TDD

Let’s make a test. In the file BasicSumTest.hs, we will import the function library BasicSum (in the lib folder), which has the function basicSum that takes two integers and return the sum, the HUnit library, and the System.Exit, which outputs the success or failure of the test:

module Main where
import BasicSum
import Test.HUnit
import qualified System.Exit as Exit
 
test1 :: Test
test1 = TestCase (assertEqual "should return 3" 3 (basicSum 1 2))
 
tests :: Test
tests = TestList [TestLabel "test1" test1]
 
main :: IO ()
main = do
    result <- runTestTT tests
    if failures result > 0 then Exit.exitFailure else Exit.exitSuccess
 
 

In the BasicSum.hs, just put the following line:

module BasicSum where

So the BasicSum library be recognized by Cabal. Now let’s run the test executing the following command in the terminal:

cabal test

The result should be this:

Build profile: -w ghc-8.10.7 -O1
In order, the following will be built (use -v for more details):
 - basic-sum-0.1.0.0 (lib:basic-sum-lib) (first run)
 - basic-sum-0.1.0.0 (test:tests) (first run)
Preprocessing library 'basic-sum-lib' for basic-sum-0.1.0.0..
Building library 'basic-sum-lib' for basic-sum-0.1.0.0..
[1 of 1] Compiling BasicSum         ( personal_info)
Configuring test suite 'tests' for basic-sum-0.1.0.0..
Preprocessing test suite 'tests' for basic-sum-0.1.0.0..
Building test suite 'tests' for basic-sum-0.1.0.0..
[1 of 1] Compiling Main             (personal_info)

tests/BasicSumTest.hs:7:52: error:
    Variable not in scope: basicSum :: t0 -> t1 -> a0
  |
7 | test1 = TestCase (assertEqual "should return 3" 3 (basicSum 1 2))
  |                                                    ^^^^^^^^

It is expected that an error occurs, don’t worry. Following the trail of TDD, you first create a test that fails, then you start to develop the code to make the test pass. Now we create the function basicSum in the BasicSum library:

module BasicSum where

basicSum :: Int -> Int -> Int
basicSum x y = x + y

Now we execute cabal test again and the result should be:

Build profile: -w ghc-8.10.7 -O1
In order, the following will be built (use -v for more details):
 - basic-sum-0.1.0.0 (lib:basic-sum-lib) (file lib/BasicSum.hs changed)
 - basic-sum-0.1.0.0 (test:tests) (dependency rebuilt)
Preprocessing library 'basic-sum-lib' for basic-sum-0.1.0.0..
Building library 'basic-sum-lib' for basic-sum-0.1.0.0..
[1 of 1] Compiling BasicSum         (personal_info)
Preprocessing test suite 'tests' for basic-sum-0.1.0.0..
Building test suite 'tests' for basic-sum-0.1.0.0..
[1 of 1] Compiling Main             (personal_info)
Linking <path_to_the_test_info_in_cabal>
Running 1 test suites...
Test suite tests: RUNNING...
Test suite tests: PASS
Test suite logged to:
<path_to_the_test_log>
1 of 1 test suites (1 of 1 test cases) passed.

The End of the Hike

That’s it! Thanks for the company! Feedback is appreciated, I am constantly improving so I can help more and more people. Until next time!

Did you like this article?

André Ferreira

Functional programming enthusiast.

See other articles by André

Related jobs

See all

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Title

The company

  • Remote

Related articles

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

•

12 Sep 2021

JavaScript Functional Style Made Simple

JavaScript Functional Style Made Simple

Daniel Boros

•

12 Sep 2021

WorksHub

CareersCompaniesSitemapFunctional WorksBlockchain WorksJavaScript WorksAI WorksGolang WorksJava WorksPython WorksRemote Works
hello@works-hub.com

Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ

108 E 16th Street, New York, NY 10003

Subscribe to our newsletter

Join over 111,000 others and get access to exclusive content, job opportunities and more!

© 2024 WorksHub

Privacy PolicyDeveloped by WorksHub