2

在 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
4

1 回答 1

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,您可以随时: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允许您触发 thunk 的评估,直到参数被完全评估。请注意,它有一个NFData(代表“普通表单数据”)约束,因此您可能会发现自己在推导您GenericNFData数据结构。


1感谢@AlexisKing 指出evaluate 确实将其参数推入 WNHF,这就是head $ map (+1) [undefined, 2, 3]触发错误的原因。在 的情况下take,这还不够。

于 2016-12-14T21:06:30.730 回答