如何在Haskell中测试错误?

时间:2012-11-12 19:27:01

标签: unit-testing testing haskell error-handling hunit

我希望能够确保函数在接收到无效值时会抛出错误。例如,假设我有一个只返回正数的函数pos:

pos :: Int -> Int
pos x
   | x >= 0 = x
   | otherwise = error "Invalid Input"

这是一个简单的例子,但我希望你明白这个想法。

我希望能够编写一个期望出现错误并将其视为通过测试的测试用例。例如:

tests = [pos 1 == 1, assertError pos (-1), pos 2 == 2, assertError pos (-2)]
runTests = all (== True) tests

[我的解决方案]

这是我最终根据@ hammar的评论进行的。

instance Eq ErrorCall where
    x == y = (show x) == (show y)

assertException :: (Exception e, Eq e) => e -> IO a -> IO ()
assertException ex action =
    handleJust isWanted (const $ return ()) $ do
        action
        assertFailure $ "Expected exception: " ++ show ex
  where isWanted = guard . (== ex) 

assertError ex f = 
    TestCase $ assertException (ErrorCall ex) $ evaluate f

tests = TestList [ (pos 0) ~?= 0
                 , (pos 1) ~?= 1
                 , assertError "Invalid Input" (pos (-1))
                 ]   

main = runTestTT tests

2 个答案:

答案 0 :(得分:3)

OP的解决方案定义了assertException,但testpack中的Test.HUnit.Tools.assertRaises似乎也可以使用。

我将msg参数添加到assertError以匹配assertRaises的工作方式,并包含选择性导入,因此像我这样的新手可以了解常用内容的导入位置。

import Control.Exception (ErrorCall(ErrorCall), evaluate)
import Test.HUnit.Base  ((~?=), Test(TestCase, TestList))
import Test.HUnit.Text (runTestTT)
import Test.HUnit.Tools (assertRaises)

pos :: Int -> Int
pos x
   | x >= 0 = x
   | otherwise = error "Invalid Input"

instance Eq ErrorCall where
    x == y = (show x) == (show y)

assertError msg ex f = 
    TestCase $ assertRaises msg (ErrorCall ex) $ evaluate f

tests = TestList [
  (pos 0) ~?= 0
  , (pos 1) ~?= 1
  , assertError "Negative argument raises an error" "Invalid Input" (pos (-1))
  ]   

main = runTestTT tests

答案 1 :(得分:0)

有几种方法可以处理Haskell中的错误。以下是概述:http://www.randomhacks.net/articles/2007/03/10/haskell-8-ways-to-report-errors

<强> [编辑]

第一个例子说明了如何捕捉错误,例如

half :: Int -> Int 
half x = if even x then x `div` 2 else error "odd"

main = do catch (print $ half 23) (\err -> print err)

那就是说,这种错误处理更适合IO的东西,像你这样的纯代码也许,或者类似的东西通常是更好的选择。它可以像......一样简单。

pos :: Int -> Maybe Int
pos x
   | x >= 0 = Just x
   | otherwise = Nothing

tests = [pos 1 == Just 1
        ,pos (-1) == Nothing
        ,pos 2 == Just 2
        ,pos (-2) == Nothing
        ]

main = print $ and tests

...如果您不需要错误类型。