Haskell单元测试

我对haskell很陌生,正在从事单元测试工作,但我发现生态系统非常混乱。 我对HTF和HUnit之间的关系感到困惑。

在一些例子中,我看到你设置了测试用例,将它们导出到一个测试列表中,然后用runTestsTT在ghci中运行(就像这个HUnit例子)。

在其他例子中,你创建一个测试运行器绑定到cabal文件中,该文件使用一些预处理器魔法来查找你的测试,就像这个git的例子。 此外,似乎HTF测试需要以test_为前缀或者它们没有运行? 我很难找到任何文档,我只注意到每个人都有的模式。

无论如何,有人可以帮我解决这个问题吗? 什么被认为是Haskell做事的标准方式? 什么是最佳实践? 什么是最容易设置和维护?


一般来说,任何重要的Haskell项目都与Cabal一起运行。 这需要关心构建,分发,文档(在haddock的帮助下)和测试。

标准方法是将测试放入test目录,然后在.cabal文件中设置一个测试套件。 这在用户手册中有详细介绍。 这是我的一个项目的测试套件的样子

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.*

然后在文件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

Utils定义了一些比HUnit更好的接口。

对于轻量级测试,我强烈建议您使用QuickCheck。 它可以让你编写简短的属性,并通过一系列随机输入来测试它们。 例如:

 -- Tests.hs
 import Test.QuickCheck

 prop_reverseReverse :: [Int] -> Bool
 prop_reverseReverse xs = reverse (reverse xs) == xs

接着

 $ ghci Tests.hs
 > import Test.QuickCheck
 > quickCheck prop_reverseReverse
 .... Passed Tests (100/100)

我也是新手哈斯克勒,我发现这个介绍真的很有用:“开始使用HUnit”。 总结一下,我将在这里给出一个没有.cabal项目文件的HUnit用法的简单测试例子:

假设我们有模块SafePrelude.hs

module SafePrelude where

safeHead :: [a] -> Maybe a
safeHead []    = Nothing
safeHead (x:_) = Just x

我们可以将测试放入TestSafePrelude.hs ,如下所示:

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]

现在使用ghc运行测试很容易:

runghc TestSafePrelude.hs

hugs - 在这种情况下, TestSafePrelude.hs必须重命名为Main.hs (据我熟悉的拥抱)(不要忘记更改模块头):

runhugs Main.hs

或任何其他haskell编译器;-)

当然,在HUnit还有更多,所以我真的推荐阅读建议的教程和库用户指南。


你已经回答了你的大部分问题,但你也问过关于HTF,以及它是如何工作的。

HTF是一个为单元测试而设计的框架 - 它与HUnit向后兼容(它集成并包装它以提供额外的功能) - 以及基于属性的测试 - 它与quickcheck集成在一起。 它使用预处理器来定位测试,以便您不必手动构建列表。 使用附注将预处理器添加到测试源文件中:

{-# OPTIONS_GHC -F -pgmF htfpp #-}

(或者,我想你可以在你的cabal文件中为你的ghc-options属性添加相同的选项,但是我从来没有试过这个,所以不知道它是否有用)。

预处理器扫描您的模块以获取名为test_xxxxprop_xxxx顶级函数,并将它们添加到模块的测试列表中。 你可以通过在模块中放置一个main函数并运行它们( main = htfMain htf_thisModuleTests )或者从模块中导出它们来直接使用这个列表,并且为多个模块提供一个主要的测试程序,这些程序导入带有测试的模块并运行所有其中:

import {-@ HTF_TESTS @-} ModuleA
import {-@ HTF_TESTS @-} ModuleB
main :: IO ()
main = htfMain htf_importedTests

该程序可以使用@jozefg描述的技术与cabal集成,或者加载到ghci中并交互式运行(尽管不在Windows上 - 请参阅https://github.com/skogsbaer/HTF/issues/60了解详细信息)。

美味是另一种提供整合不同类型测试的方法。 它没有像HTF这样的预处理器,但是有一个使用Template Haskell执行类似功能的模块。 像HTF一样,它也依赖命名约定来识别你的测试(在这种情况下, case_xxxx而不是test_xxxx )。 除了HUnit和QuickCheck测试外,它还具有处理多种其他测试类型的模块。

链接地址: http://www.djcxy.com/p/80769.html

上一篇: Haskell unit testing

下一篇: StackOverflowExceptions in nested async methods on unwinding of the stack