gpt4 book ai didi

unit-testing - Haskell 异常和单元测试

转载 作者:行者123 更新时间:2023-12-01 10:01:59 24 4
gpt4 key购买 nike

基于 SO 问题 13350164 How do I test for an error in Haskell? ,我正在尝试编写一个单元测试,断言给定无效输入,递归函数会引发异常。我采用的方法适用于非递归函数(或者当第一次调用引发异常时),但一旦异常发生在调用链的更深处,断言就会失败。

我已经阅读了问题 6537766 Haskell approaches to error handling 的优秀答案但不幸的是,对于我学习曲线的这一点来说,这个建议有点过于笼统。我的猜测是这里的问题与延迟评估和非纯测试代码有关,但我希望得到专家的解释。

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

这是我想出的代码。前两个测试用例成功,但第三个失败并显示 “未收到异常,但预期异常:负项”

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中的一个错误。当唯一的负数是列表中的最后一个时,您的代码不会进行否定性检查——第二个分支不包括检查。

值得注意的是,像您这样编写递归的规范方式会破坏“空性”测试以避免此错误。通常,将您的解决方案分解为“sum”加两个守卫将有助于避免错误。


我支持 Haskell approaches to error handling 的建议顺便一提。 Control.Exception 更难推理和学习,error 应该只用于标记不可能实现的代码分支——我更喜欢一些建议,它应该被称为不可能

为了使建议切实可行,我们可以使用 Maybe 重新构建此示例。首先,内置了不 protected 函数:

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

我们可以将它们组合成一个 monad

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] 如果任何包含的值是 Nothing 则整个列表失败。从 sequence 开始,我们实际上可以更进一步。 map f 是一个常见的成语

negativeGivesNothing = mapM (elseNothing (>= 0))

所以,最后

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

关于unit-testing - Haskell 异常和单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14802640/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com