0

Background

Using stack, and its preset file Spec.hs, as far as I know you need to import the following test framework modules in order to execute a proper test:

import qualified Test.Framework as TF
import qualified Test.Framework.Providers.HUnit as FHU
import qualified Test.Framework.Providers.QuickCheck2 as QC2
import qualified Test.HUnit as HU
import qualified Test.QuickCheck as QC

Hence, you also need to add the add dependecies into the package.yaml file, as follows:

tests:
  XYZ-test:
    main:                Spec.hs
    source-dirs:         test
    ghc-options:
    - -threaded
    - -rtsopts
    - -with-rtsopts=-N
    dependencies:
    - Test4
    - test-framework
    - test-framework-hunit
    - test-framework-quickcheck2
    - HUnit
    - QuickCheck

If you import the subject to test (calling it MyModule) and implement test cases in Spec.hs for that module then you cannot test the functions that are internally used in the module (MyModule).

To test the internal functions you could implement the tests inside the module (MyModule) and export the tests.

module MyModule
    (
        ...
        testCases, -- exported test cases
        -- fun1 -- internal function not exported
    ) where

...

import qualified Test.Framework as TF
import qualified Test.Framework.Providers.HUnit as FHU
import qualified Test.HUnit as HU

fun1 :: [Bool] -> Integer -- internal function not exported
fun1 ...

testCases =
    (FHU.testCase "MyModule.fun1 #1" ((fun1 []) HU.@?= 0)) : 
    (FHU.testCase "MyModule.fun1 #2" ((fun1 [True]) HU.@?= 0)) : 
    (FHU.testCase "MyModule.fun1 #2" ((fun1 [True, True]) HU.@?= 2)) : 
    []

But then you also need to import the test framework (at least, Test.Framework, Test.Framework.Providers.HUnit, and Test.HUnit) and need to add additional dependencies also to the library of (MyModule). Hence, package.yaml would look like this:

...
dependencies:
- ...
- test-framework
- test-framework-hunit
- HUnit

library:
  source-dirs: src
...

Question

Is there a more lean aproach to export the unit test of module MyModule?

Jörg Brüggmann
  • 603
  • 7
  • 19

1 Answers1

0

Add a data type to wrap the bindings for each test case. Ideally, in a module (e.g. TestCaseWrap) to reuse that for other modules with internal functions that are to be tested.

{-| wraps a test case

* prefix: tcw, TCW
* for an assertion
  * to hold/evaluate the test case name
  * to hold/evaluate the actual value of test case
  * to hold/evaluate the expected value of test case
-}
data TestCaseWrap a = 
    TestAssertion {
        -- | name of the test case, needed to reference and find the test case if failed
        rsName :: String, 
        -- | function and actual value evaluated by test case, respectively
        rxActual :: a, 
        -- | expected value evaluated by test case, respectively
        rxExpected :: a }

NOTE: The data structure TestCaseWrap supports assertions and may be extended for proballistic quick tests.

In module MyModule import TestCaseWrap to define the data type TestCaseWrap. Fill an array with all the test cases (e.g. testCasesWrap).

module MyModule
    (
        ...
        lTestCasesWrap 
    ) where

import qualified TestCaseWrap as TCW

...

fun1 :: [Bool] -> Integer
fun1 ...

testCasesWrap :: [TCW.TestCaseWrap Integer]
testCasesWrap =
    (TCW.TestAssertion "MyModule.fun1 #1" (fun1 []) 0) :
    (TCW.TestAssertion "MyModule.fun1 #2" (fun1 [True]) 0) : 
    (TCW.TestAssertion "MyModule.fun1 #3" (fun1 [True, True]) 2) : 
    []

Implement a function to convert the wrapped test information into a Test. Again, ideally, in a module (e.g. TestCaseUnwrap).

module TestCaseUnwrap
    (
        testCaseFromTestCaseWrap, 
        testCaseFromTestCaseWrapList
    ) where

import qualified TestCaseWrap as TCW

import qualified Test.Framework as TF
import qualified Test.Framework.Providers.HUnit as FHU
import qualified Test.HUnit as HU

testCaseFromTestCaseWrap :: (Eq a, Show a) => (TCW.TestCaseWrap a) -> TF.Test
testCaseFromTestCaseWrap (TCW.TestAssertion sName xActual xExpected) = FHU.testCase sName (xActual HU.@?= xExpected)

testCaseFromTestCaseWrapList :: (Eq a, Show a) => [TCW.TestCaseWrap a] -> [TF.Test]
testCaseFromTestCaseWrapList ltcwA = fmap testCaseFromTestCaseWrap ltcwA

Implement Spec.hs like this:

import qualified Test.Framework as TF
import qualified Test.Framework.Providers.HUnit as FHU
import qualified Test.Framework.Providers.QuickCheck2 as QC2
import qualified Test.HUnit as HU
import qualified Test.QuickCheck as QC

import qualified MyModule
import qualified TestCaseUnwrap as TCU

main :: IO ()
main = 
    TF.defaultMainWithOpts
        (
            (TCU.testCaseFromTestCaseWrapList  MyModule.testCasesWrap) ++ 
            [
                ... -- other tests
            ]
        )
        mempty

... -- other tests

... and it will execute like this:

XYZ> test (suite: XYZ-test)

MyModule.fun1  #1: [OK]
MyModule.fun1  #2: [OK]
...
... : [OK]
... : [OK]
... : [OK]


         Properties  Test Cases   Total
 Passed  1           71           72
 Failed  0           0            0
 Total   1           71           72

XYZ> Test suite XYZ-test passed
Completed 2 action(s).
Jörg Brüggmann
  • 603
  • 7
  • 19