在Haskell中对惰性表达式中的undefined进行单元测试

时间:2016-12-14 20:50:38

标签: unit-testing haskell hspec

在遇到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

1 个答案:

答案 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;)约束,因此您可能会发现自己为您的数据结构派生GenericNFData

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