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