aka*_*ola 3 haskell unit-testing exception
基于SO问题13350164 如何在Haskell中测试错误?,我正在尝试编写一个单元测试,断言给定无效输入,递归函数引发异常.我采用的方法适用于非递归函数(或者当第一次调用引发异常时),但是一旦异常发生在调用链中,断言就会失败.
我已经阅读了问题6537766 Haskell错误处理方法的优秀答案,但不幸的是,对于我的学习曲线的这一点,建议有点过于通用.我的猜测是这里的问题与懒惰评估和非纯测试代码有关,但我很欣赏专家的解释.
在这种情况下(例如Maybe或者Either),我应该采用不同的方法来处理错误,还是在使用这种风格时是否有合理的方法使测试用例正常工作?
这是我提出的代码.前两个测试用例成功,但第三个测试用例失败了"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
Run Code Online (Sandbox Code Playgroud)
它实际上只是一个错误sumPositiveInts.您的代码也没有的时候才会负数是最后一个列表,第二支不包括检查做消极检查.
值得注意的是,像你这样编写递归的规范方法会打破"空虚"测试,以避免这个错误.通常,将您的解决方案分解为"总和"加上两个警卫将有助于避免错误.
我是第二个Haskell建议的方法来处理错误.Control.Exception更难以推理和学习,error只应用于标记无法实现的代码分支 - 我更喜欢它应该被称为的一些建议impossible.
为了使建议有形,我们可以使用重建此示例Maybe.首先,无人看守的功能是内置的:
sum :: Num a => [a] -> a
Run Code Online (Sandbox Code Playgroud)
然后我们需要建立两个警卫(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
Run Code Online (Sandbox Code Playgroud)
我们可以将它们组合成一个单子
sumPositiveInts :: [a] -> Maybe a
sumPositiveInts xs = do xs1 <- emptyIsNothing xs
xs2 <- negativeGivesNothing xs1
return (sum xs2)
Run Code Online (Sandbox Code Playgroud)
然后我们可以使用许多习语和缩减来使这些代码更容易阅读和编写(一旦你知道约定!).让我强调,在这一点之后没有必要,也不是非常容易理解.学习它可以提高你分解函数的能力,并且流利地考虑FP,但我只是跳到高级的东西.
例如,我们可以使用"Monadic (.)"(也称为Kleisli箭头组合)来编写sumPositiveInts
sumPositiveInts :: [a] -> Maybe a
sumPositiveInts = emptyIsNothing >=> negativeGivesNothing >=> (return . sum)
Run Code Online (Sandbox Code Playgroud)
我们可以简化两者emptyIsNothing并negativeGivesNothing使用组合器
elseNothing :: (a -> Bool) -> a -> Just a
pred `elseNothing` x | pred x = Just x
| otherwise = Nothing
emptyIsNothing = elseNothing null
negativeGivesNothing = sequence . map (elseNothing (>= 0))
Run Code Online (Sandbox Code Playgroud)
其中,sequence :: [Maybe a] -> Maybe [a]如果任何包含的值是失败的整个列表Nothing.我们实际上可以更进一步,因为这sequence . map f是一个常见的习语
negativeGivesNothing = mapM (elseNothing (>= 0))
Run Code Online (Sandbox Code Playgroud)
所以,最后
sumPositives :: [a] -> Maybe a
sumPositives = elseNothing null
>=> mapM (elseNothing (>= 0))
>=> return . sum
Run Code Online (Sandbox Code Playgroud)