在 Haskell 中对惰性表达式中未定义的值进行单元测试

标签 unit-testing haskell hspec

在 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

无济于事。它报告我没有得到预期的异常: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

最佳答案

问题是 evaluate不会强制您的表达式为 NH 甚至 WHNF1。试试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 允许您触发 thunk 的求值,直到参数被完全求值。请注意,它有一个 NFData (代表“正常形式数据”)对其进行约束,因此您可能会发现自己导出 GenericNFData用于您的数据结构。


1 感谢@AlexisKing 指出evaluate 确实将其参数插入 WNHF,这就是为什么 head $ map (+1) [undefined, 2, 3]确实触发了错误。以take为例,但这还不够。

关于在 Haskell 中对惰性表达式中未定义的值进行单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41151927/

相关文章:

winapi - 从 Haskell 访问 64 位注册表

haskell - Curry-Howard 对应于双重否定 ((a->r)->r) 或 ((a->⊥)->⊥) 吗?

haskell - 使用 HSpec 和 QuickCheck 验证 Data.Monoid 属性

visual-studio - 如何重新运行单元测试 X 次?

java - Flink 中复杂拓扑(多输入)的集成测试

python - 如何构建Python模块以使其既可扩展又可测试?

angularjs - AngularJS 中的单元测试错误 - 具有许多依赖项的 Controller

haskell - Yampa 中 react (感觉)功能的时差

Haskell - 断言一个函数被调用

haskell - 从 Cabal 向 HSpec 提供选项