【问题标题】:Haskell unit testingHaskell 单元测试
【发布时间】:2013-12-02 14:55:53
【问题描述】:

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

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

在其他示例中,您创建了一个绑定到 cabal 文件的测试运行程序,该文件使用一些预处理器魔法来查找您的测试,例如 git example。此外,HTF 测试似乎需要以test_ 为前缀,否则它们不会运行?我很难找到任何文档,我只是注意到每个人都有的模式。

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

【问题讨论】:

  • 您看过 QuickCheck 库吗?我一直觉得它很容易使用。
  • 是的,但快速检查是一个不同的用例,用于基于类型的测试,这不是我现在想要的。我很想知道如何整合它,尽管一旦我了解 htf 和 hunit 的关系
  • twitter.com/HaskellTips/status/425793151660331008 说更喜欢tasty 而不是test-framework(HTF?),但我也看到HTF 在经历了几个月的沉寂后,上周有了一个小更新。

标签: unit-testing haskell


【解决方案1】:

通常,任何重要的 Haskell 项目都使用 Cabal 运行。这负责构建、分发、文档(在黑线鳕的帮助下)和测试。

标准方法是将测试放在test 目录中,然后在.cabal 文件中设置测试套件。这在user manual 中有详细说明。这是我的一个项目的测试套件的样子

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)

【讨论】:

  • 随着项目的发展,您必须维护导出的测试列表吗?似乎容易出错。我想我仍然对这与自动导出的预处理器方法有何关系感到困惑?我看到很多单元测试示例,但它们都不同
  • @devshorts 测试列表让您可以单独命名每个测试。我确实相信有些框架会自动运行您的测试,但我通常每个文件大约有 10 个测试,因此维护如此小的列表非常容易。
  • 您能否详细说明如何将这种方法用于多个测试文件?主运行器是单独的还是通常是测试夹具的一部分?
  • @devshorts 我将测试分开,并从每个模块中导出带有名称的测试列表。然后主要是合并列表并运行它们。
  • 我用这个答案为这个小项目编写了测试框架:github.com/siddharthist/m3u-convert 感谢@jozefg,希望这个例子对大家有帮助:-)
【解决方案2】:

我也是新手 Haskeller,我发现这个介绍真的很有帮助:“Getting started with 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 中还有更多内容,所以我真的建议阅读建议的教程和库User's Guide

【讨论】:

  • 您好,HUnit 入门链接已损坏
【解决方案3】:

您已经回答了大部分问题,但您也询问了 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)。

Tasty 是另一种替代方法,它提供了一种集成不同类型测试的方法。它没有像 HTF 这样的预处理器,但有一个 performs similar functions using Template Haskell 的模块。与 HTF 一样,它也依赖于命名约定来识别您的测试(在这种情况下,case_xxxx 而不是 test_xxxx)。除了 HUnit 和 QuickCheck 测试之外,它还具有用于处理许多其他测试类型的模块。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-08
    • 1970-01-01
    • 1970-01-01
    • 2011-07-07
    相关资源
    最近更新 更多