Модульное тестирование неопределенного значения в ленивом выражении в Haskell
Написание модульного теста в Haskell, где выражение должно потерпеть неудачу, когда undefined
встречается немного сложнее. Я попробовал следующее с HSpec:
module Main where
import Test.Hspec
import Control.Exception (evaluate)
main :: IO ()
main = hspec $ do
describe "Test" $ do
it "test case" $ do
evaluate (take 1 $ map (+1) [undefined, 2, 3]) `shouldThrow` anyException
но безрезультатно. Сообщает мне did not get expected exception: SomeException
Если я вычислю то же выражение в REPL, я получу:
[*** Exception: Prelude.undefined
CallStack (from HasCallStack):
error, called at libraries\base\GHC\Err.hs:79:14 in base:GHC.Err
undefined, called at <interactive>:2:20 in interactive:Ghci1
1 ответ
Проблема в том, что evaluate
не заставляет ваше выражение к NH или даже WHNF 1. Пытаться x <- evaluate (take 1 $ map (+1) [undefined, 2, 3])
в GHCi - это не дает вам никакой ошибки! Единственная причина, по которой это происходит, когда вы вставляете evaluate (take 1 $ map (+1) [undefined, 2, 3])
в том, что GHCi также пытается напечатать результат того, что он получил, и, для этого, он пытается вычислить выражение.
Если вы хотите увидеть, сколько из thunk был оценен, вы всегда можете использовать :sprint
в GHCi:
ghci> x <- evaluate (take 1 $ map (+1) [undefined, 2, 3])
ghci> :sprint x
x = [_]
Как вы видете, evaluate
не заставил выражение достаточно далеко, чтобы понять, x
содержит undefined
, Быстрое решение состоит в том, чтобы оценить то, что вы исследуете, в обычной форме, используя force
,
import Test.Hspec
import Control.Exception (evaluate)
import Control.DeepSeq (force)
main :: IO ()
main = hspec $ do
describe "Test" $ do
it "test case" $ do
evaluate (force (take 1 $ map (+1) [undefined, 2, 3] :: [Int]))
`shouldThrow` anyException
force
позволяет запускать оценку thunks, пока аргумент не будет полностью оценен. Обратите внимание, что у него есть NFData
(означает "обычные данные формы"), поэтому вы можете получить Generic
а также NFData
для ваших структур данных.
1 Спасибо за @AlexisKing за указание на то, что evaluate
толкает свой аргумент в WNHF, поэтому head $ map (+1) [undefined, 2, 3]
действительно вызывает ошибку. В случае take
, этого недостаточно, хотя.