Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell unit testing

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?

like image 415
devshorts Avatar asked Dec 02 '13 14:12

devshorts


People also ask

What is Hspec?

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.

What is tasty test?

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.


2 Answers

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) 
like image 68
Daniel Gratzer Avatar answered Oct 18 '22 16:10

Daniel Gratzer


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.

like image 28
paluh Avatar answered Oct 18 '22 16:10

paluh