[英]Haskell exceptions and unit testing
基於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
它實際上只是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)
我們可以使用組合器簡化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 :: [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.