Testing with HUnit

Contents
A test suite
Test cases
Run the tests
Why pure code tests well

A test suite

A test suite is a second build target in the same .cabal file. It depends on the library and on the test framework, and it has its own main:

test-suite todo-test type: exitcode-stdio-1.0 main-is: Spec.hs hs-source-dirs: test build-depends: base, todo, HUnit >=1.6 default-language: Haskell2010

Keeping the logic in the library is what makes this stanza short. main in the test file only builds and runs the assertions.

Test cases

HUnit offers three ingredients: a label, an assertion with ~?=, and a list of cases:

import Test.HUnit import Todo (addItem, countItems) tests = TestList [ "empty list has no items" ~:? TestCase (countItems [] ~?= 0) , "one item" ~:? TestCase (countItems (addItem "milk" []) ~?= 1) ]

~?= compares two values and reports both sides when they differ. ~:? attaches a label, which is the line you will read at two in the morning.

Run the tests

The test main prints a summary and exits non-zero on failure, which is what a continuous integration job needs:

main :: IO () main = do counts <- runTestTT tests when (errors counts + failures counts > 0) exitFailure

cabal test

Cases: 2 Tried: 2 Errors: 0 Failures: 0

cabal test --test-show-details=direct prints the output as it happens instead of hiding it until the end, which matters for a failing case.

Why pure code tests well

Every function in the library is a value transformer, so a test is an expression and needs no setup, no mock, and no teardown:

-- the whole test is one comparison TestCase (shout "hi" ~?= "HI")

The IO shell from lesson 33 stays thin precisely so that most of the program lands here. Property based testing in the next lesson takes the same advantage one step further.

Next: Property testing with QuickCheck

Article author: Arthur Isaev

Related articles
Haskell
Haskell course: zero to hero in 50 lessons
What is Haskell and why it matters
Install the toolchain with GHCup
GHCi: the interactive shell
Your first program
Values with let and where
Types and type signatures
Functions and application
Currying and partial application
Operators, sections, and fixity
if, guards, and pattern guards
case expressions
Pattern matching
Tuples
Lists
Ranges and enumeration
List comprehensions
Recursion replaces loops
map, filter, and foldr
Lambdas and composition
Type classes
Parametric polymorphism
Maybe and null safety
Either and recoverable errors
data: product types
data: sum types
Records
newtype and type synonyms
Recursive data: binary trees
Functor
Applicative
Monad
do notation
IO and purity
Files and IO
Modules and imports
Cabal projects
Dependencies
Testing with HUnit
Property testing with QuickCheck
Laziness and infinite lists
Strictness and space leaks
Folds
Monoids
Traversable
Monad transformers
Errors and exceptions
Concurrency
Parsing with Megaparsec
Capstone: a todo CLI
Hero roadmap: the whole course on one page

Search this site

Channel @aofeed Chat @aofeedchat

Contacts and cooperation:
I recommend our hosting beget.com
Write to info@aredel.com if you:
1. Want to write an article for our site or translate an article into your native language.
2. Want to place thematically relevant ads on the site.
3. Ads on my site pass maximum censorship. If you see an ad block unsuitable for school-age children, shocking or misleading - please contact us by e-mail
4. Found a mistake, inaccuracy, bug, etc. on the site. ... .......
5. Articles can be shared on social media by clicking a network icon: