对模块内部功能进行单元测试,同时避免对测试框架和 HUnit 的依赖

问题描述

背景

使用堆栈及其预设文件Spec.hs,据我所知您需要导入以下测试框架模块才能执行正确的测试:

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

因此,您还需要将添加依赖项添加package.yaml文件中,如下所示:

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

如果您导入要测试的主题(称为 MyModule)并在 Spec.hs 中为该模块实现测试用例,则您无法测试模块内部使用的函数({{1} }).

要测试内部函数,您可以在模块 (MyModule) 内实现测试并导出测试。

MyModule

但是您还需要导入测试框架(至少,Test.Framework、Test.Framework.Providers.HUnit 和 Test.HUnit)并且还需要向 ({{1} })。因此,package.yaml 将如下所示:

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)) : 
    []

问题

是否有更精简的方法来导出模块 MyModule 的单元测试?

解决方法

添加数据类型以包装每个测试用例的绑定。理想情况下,在一个模块(例如 TestCaseWrap)中,将其重用于其他具有要测试的内部功能的模块。

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

library:
  source-dirs: src
...

注意:数据结构 MyModule 支持断言,并且可以为概率快速测试进行扩展。

在模块 {-| 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 } 中导入 TestCaseWrap 以定义数据类型 MyModule。用所有测试用例填充一个数组(例如 TestCaseWrap)。

TestCaseWrap

实现一个函数,将包装好的测试信息转换为 testCasesWrap。同样,理想情况下,在模块中(例如 TestCaseUnwrap)。

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) : 
    []

像这样实现 Spec.hs:

Test

...它会像这样执行:

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