在遇到undefined
时表达式失败的Haskell中编写单元测试有点棘手。我用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
答案 0 :(得分:4)
问题是evaluate
不会强制你的表达到NH 甚至WHNF 1 。在GHCi中尝试x <- evaluate (take 1 $ map (+1) [undefined, 2, 3])
- 它不会给你任何错误!粘贴在evaluate (take 1 $ map (+1) [undefined, 2, 3])
时它唯一的原因是GHCi还试图打印它所得到的结果,为此,它最终试图评估表达式。
如果您想查看已评估了多少thunk,可以在GHCi中始终使用:sprint
:
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
允许您触发对thunk的评估,直到参数完全评估为止。请注意,它有一个NFData
(代表&#34;普通表单数据&#34;)约束,因此您可能会发现自己为您的数据结构派生Generic
和NFData
。
1 感谢@AlexisKing指出evaluate
将其参数推送到WNHF,这就是head $ map (+1) [undefined, 2, 3]
确实触发错误的原因。在take
的情况下,它虽然不够。