Haskell单元测试


77

我是Haskell的新手,正在从事单元测试,但是我发现生态系统非常混乱。我对HTF和HUnit之间的关系感到困惑。

在某些示例中,我看到您设置了测试用例,将其导出到测试列表中,然后在ghci中运行runTestsTT如此HUnit示例)。

在其他示例中,您将创建一个绑定到cabal文件的测试运行器,该运行器使用一些预处理器魔术来查找您的测试,如git示例所示。同样,似乎需要在HTF测试前面加上前缀,test_否则它们将无法运行?我很难找到关于它的任何文档,我只是注意到每个人都有的模式。

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


您是否看过QuickCheck库?我一直发现它很容易使用。
bheklilr 2013年

4
是的,但是快速检查是一个不同的用例,用于基于类型的测试,这不是我现在想要的。我很想知道如何整合它,尽管我一头绕过htf和hunit的关系
就好了

twitter.com/HaskellTips/status/425793151660331008说喜欢tastytest-framework(HTF?),但我也看到,HTF上周得到了一个小更新,几个月quiey后。
misterbee 2014年

Answers:


48

通常,任何重要的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)

6
随着项目的发展,您必须维护导出的测试列表吗?似乎容易出错。我想我仍然对这与自动导出的预处理器方法有何关系感到困惑?我看到了很多单元测试示例,但都不同
devshorts

@devshorts测试列表使您可以分别命名每个测试。我确实相信有些框架可以自动运行您的测试,但是我通常每个文件约有10个测试,因此维护如此小的列表非常容易。
Daniel Gratzer 2013年

1
您能否详细说明如何将这个方法用于多个测试文件?主流道是单独的还是通常是测试夹具的一部分?
devshorts 2013年

1
@devshorts我将测试分开,并从每个模块中导出测试列表及其名称。然后在主要方面,我合并列表并运行它们。
Daniel Gratzer 2013年

我用这个答案为这个小项目编写了测试框架:github.com/siddharthist/m3u-convert感谢@jozefg,我希望这个例子能对所有人有所帮助:-)
Langston

34

我也是新手haskeller,我发现此介绍非常有帮助:“ 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,因此,我真的建议阅读建议的教程和库用户指南


1
你好,链接到入门HUnit被打破
sandwood

4

您已经回答了大多数问题,但您还询问了HTF及其工作原理。

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

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

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

预处理程序会在模块中扫描名为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测试外,它还具有用于处理许多其他测试类型的模块。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.