簡體   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