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_xxxx
或prop_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测试外,它还具有处理多种其他测试类型的模块。
上一篇: Haskell unit testing
下一篇: StackOverflowExceptions in nested async methods on unwinding of the stack