I'm new to haskell and working on unit testing, however I find the ecosystem to be very confusing. I'm confused as to the relationship between HTF and HUnit.
In some examples I see you set up test cases, export them in an tests list, and then run in ghci with runTestsTT
(like this HUnit example).
In other examples, you create a test runner tied into the cabal file that uses some preprocessor magic to find your tests like in this git example. Also it seems that HTF tests need to be prefixed with test_
or they aren't run? I had a hard time finding any documentation on that, I just noticed the pattern that everyone had.
Anyways, can someone help sort this out for me? What is considered the standard way of doing things in Haskell? What are the best practices? What is the easiest to set up and maintain?
Hspec is a testing framework for Haskell. Some of Hspec's distinctive features are: a friendly DSL for defining tests. integration with QuickCheck, SmallCheck, and HUnit. parallel test execution.
Tasty is a modern testing framework for Haskell. It lets you combine your unit tests, golden tests, QuickCheck/SmallCheck properties, and any other types of tests into a single test suite. Features: Run tests in parallel but report results in a deterministic order.
Generally, any significant Haskell project is run with Cabal. This takes care of building, distribution, documentation (with the help of haddock), and testing.
The standard approach is to put your tests in the test
directory and then set up a test suite in your .cabal
file. This is detailed in the user manual. Here's what the test suite for one of my projects looks like
Test-Suite test-melody type: exitcode-stdio-1.0 main-is: Main.hs hs-source-dirs: test build-depends: base >=4.6 && <4.7, test-framework, test-framework-hunit, HUnit, containers == 0.5.*
Then in the file test/Main.hs
import Test.HUnit import Test.Framework import Test.Framework.Providers.HUnit import Data.Monoid import Control.Monad import Utils pushTest :: Assertion pushTest = [NumLit 1] ^? push (NumLit 1) pushPopTest :: Assertion pushPopTest = [] ^? (push (NumLit 0) >> void pop) main :: IO () main = defaultMainWithOpts [testCase "push" pushTest ,testCase "push-pop" pushPopTest] mempty
Where Utils
defines some nicer interfaces over HUnit.
For lighter-weight testing, I strongly recommend you use QuickCheck. It lets you write short properties and test them over a series of random inputs. For example:
-- Tests.hs import Test.QuickCheck prop_reverseReverse :: [Int] -> Bool prop_reverseReverse xs = reverse (reverse xs) == xs
And then
$ ghci Tests.hs > import Test.QuickCheck > quickCheck prop_reverseReverse .... Passed Tests (100/100)
I'm also newbie haskeller and I've found this introduction really helpful: "Getting started with HUnit". To summarize, I'll put here simple testing example of HUnit usage without .cabal
project file:
Let's assume that we have module SafePrelude.hs
:
module SafePrelude where safeHead :: [a] -> Maybe a safeHead [] = Nothing safeHead (x:_) = Just x
we can put tests into TestSafePrelude.hs
as follows:
module TestSafePrelude where import Test.HUnit import SafePrelude testSafeHeadForEmptyList :: Test testSafeHeadForEmptyList = TestCase $ assertEqual "Should return Nothing for empty list" Nothing (safeHead ([]::[Int])) testSafeHeadForNonEmptyList :: Test testSafeHeadForNonEmptyList = TestCase $ assertEqual "Should return (Just head) for non empty list" (Just 1) (safeHead ([1]::[Int])) main :: IO Counts main = runTestTT $ TestList [testSafeHeadForEmptyList, testSafeHeadForNonEmptyList]
Now it's easy to run tests using ghc
:
runghc TestSafePrelude.hs
or hugs
- in this case TestSafePrelude.hs
has to be renamed to Main.hs
(as far as I'm familiar with hugs) (don't forget to change module header too):
runhugs Main.hs
or any other haskell
compiler ;-)
Of course there is more then that in HUnit
, so I really recommend to read suggested tutorial and library User's Guide.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With