Модульное тестирование внутренних функций модуля, избегая при этом зависимостей от тестовой среды и 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 для этого модуля, вы не сможете протестировать функции, которые используются внутри модуля (MyModule).

Для тестирования внутренних функций вы можете реализовать тесты внутри модуля (MyModule) и экспортировать тесты.

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

Но тогда вам также необходимо импортировать тестовую среду (по крайней мере, Test.Framework, Test.Framework.Providers.HUnit и Test.HUnit) и добавить дополнительные зависимости также в библиотеку (MyModule). Следовательно, package.yaml будет выглядеть так:

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

library:
  source-dirs: src
...

Вопрос

Есть ли более экономичный подход к экспорту модульного теста модуля MyModule?


person Jörg Brüggmann    schedule 09.06.2021    source источник


Ответы (1)


Добавьте тип данных, чтобы обернуть привязки для каждого теста. В идеале, в модуле (например, TestCaseWrap), чтобы повторно использовать его для других модулей с внутренними функциями, которые необходимо протестировать.

{-| 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).

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

Реализуйте функцию для преобразования упакованной тестовой информации в файл Test. Опять же, в идеале, в модуле (например, 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

Реализуйте 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

import qualified MyModule
import qualified TestCaseUnwrap as TCU

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

... -- other tests

... и он будет выполняться так:

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).
person Jörg Brüggmann    schedule 09.06.2021