繁体   English   中英

Haskell异常和单元测试

[英]Haskell exceptions and unit testing

基于SO问题13350164 如何在Haskell中测试错误? ,我正在尝试编写一个单元测试,断言给定无效输入,递归函数引发异常。 我采用的方法适用于非递归函数(或者当第一个调用引发异常时),但是一旦异常发生在调用链中,断言就会失败。

我已经阅读了问题6537766 Haskell错误处理方法的优秀答案,但不幸的是,对于我的学习曲线的这一点,建议有点过于通用。 我的猜测是这里的问题与懒惰评估和非纯测试代码有关,但我很欣赏专家的解释。

我应该采取不同的方法来处理这种情况下的错误处理(例如, MaybeEither ),还是在使用这种风格时是否有合理的解决办法使测试用例正常工作?

这是我提出的代码。 前两个测试用例成功,但第三个测试用例失败, "Received no exception, but was expecting exception: Negative item"

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

sumPositiveInts :: [Int] -> Int
sumPositiveInts [] = error "Empty list"
sumPositiveInts (x:[]) = x
sumPositiveInts (x:xs) | x >= 0 = x + sumPositiveInts xs
                       | otherwise = error "Negative item"

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

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

tests = TestList [
  assertError "Empty" "Empty list" (sumPositiveInts ([]))
  , assertError "Negative head" "Negative item" (sumPositiveInts ([-1, -1]))
  , assertError "Negative second item" "Negative item" (sumPositiveInts ([1, -1]))
  ]   

main = runTestTT tests

它实际上只是sumPositiveInts一个错误。 当只有负数是最后一个列表,第二支不包括检查你的代码没有消极检查。

值得注意的是,像你这样编写递归的规范方法会打破“空虚”测试,以避免这个错误。 通常,将您的解决方案分解为“总和”加上两个警卫将有助于避免错误。


我是第二个Haskell建议的方法来处理错误 Control.Exception更难以推理和学习,并且error只应用于标记无法实现的代码分支 - 我更喜欢一些应该被称为impossible

为了使建议有形,我们可以使用Maybe重建这个例子。 首先,无人看守的功能是内置的:

sum :: Num a => [a] -> a

然后我们需要建立两个守卫(1)空列表给出Nothing和(2)包含负数的列表给出Nothing

emptyIsNothing :: [a] -> Maybe [a]
emptyIsNothing [] = Nothing
emptyIsNothing as = Just as

negativeGivesNothing :: [a] -> Maybe [a]
negativeGivesNothing xs | all (>= 0) xs = Just xs
                        | otherwise     = Nothing

我们可以将它们组合成一个单子

sumPositiveInts :: [a] -> Maybe a
sumPositiveInts xs = do xs1 <- emptyIsNothing xs
                        xs2 <- negativeGivesNothing xs1
                        return (sum xs2)

然后我们可以使用许多习语和缩减来使这些代码更容易阅读和编写(一旦你知道约定!)。 让我强调, 在这一点之后没有必要,也不是非常容易理解。 学习它可以提高你分解函数的能力,并且流利地考虑FP,但我只是跳到高级的东西。

例如,我们可以使用“Monadic (.) ”(也称为Kleisli箭头组合)来编写sumPositiveInts

sumPositiveInts :: [a] -> Maybe a
sumPositiveInts = emptyIsNothing >=> negativeGivesNothing >=> (return . sum)

我们可以使用组合器简化emptyIsNothingnegativeGivesNothing

elseNothing :: (a -> Bool) -> a -> Just a
pred `elseNothing` x | pred x    = Just x
                     | otherwise = Nothing

emptyIsNothing = elseNothing null

negativeGivesNothing = sequence . map (elseNothing (>= 0))

其中sequence :: [Maybe a] -> Maybe [a]如果任何包含的值为Nothingsequence :: [Maybe a] -> Maybe [a]失败整个列表。 实际上,我们可以从sequence . map f更进一步sequence . map f sequence . map f是一种常见的习语

negativeGivesNothing = mapM (elseNothing (>= 0))

所以,最后

sumPositives :: [a] -> Maybe a
sumPositives = elseNothing null 
               >=> mapM (elseNothing (>= 0))
               >=> return . sum

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM