【问题标题】:Haskell Pipes and testing with HSpecHaskell 管道和使用 HSpec 进行测试
【发布时间】:2016-08-27 15:49:20
【问题描述】:

我为一个使用 Pipes 的项目编写了一个程序,我很喜欢它!但是,我正在努力对我的代码进行单元测试。

我有一系列 Pipe In Out IO () 类型的函数(例如),我希望使用 HSpec 进行测试。我该怎么办?

例如,假设我有这个域:

data Person = Person String Int | Unknown deriving (Show, Eq)
data Classification = Friend | Foe | Undecided deriving Show

还有这个管道:

classify :: Pipe Person (Person, Classification) IO ()
classify = do
    p@(Person name _) <- await
    case name of 
      "Alex" -> yield (p, Friend)
      "Bob" -> yield (p, Foe)
      _ -> yield (p, Undecided)

我想写一个规范:

main = hspec $ do
  describe "readFileP" $ 
    it "yields all the lines of a file"
      pendingWith "How can I test this Pipe? :("

【问题讨论】:

  • 将您的管道转换为生产者或效果器,并使用toListM 或简单地使用runEffect 来实现值。显然,您必须决定如何为管道创建和提供测试数据。
  • runEffect 只会给我一个m r,在这种情况下是IO ()。不确定这应该有什么帮助?
  • 不是runEffect classify,而是runEffect (giveDataToClassify classify)——就像我说的,你的管道接受一个输入,你必须决定什么输入是什么,只需将你的管道组合成适当的使用管道创建输出而不需要输入的适当方式(我认为在pipes 这是Producer)。例如,toListM $ mapM_ yield [ Person "Bob" 10, Person "June" 20 ] &gt;-&gt; classify 做你想做的事吗?注意\xs -&gt; toListM $ mapM_ yield xs &gt;-&gt; classify 的类型是[Person] -&gt; IO [(Person, Classification)],这在我看来是一种与 HSpec 兼容的形式。
  • 我知道我需要向管道提供数据。我的问题是runEffect 调用在IO monad 中,它无法评估为hspec 的it 所需的Expectation
  • 查看type of it,它需要一个Example a =&gt; a 参数,并且您有一个Example Expectation 的实例(即Example (IO ())),其语义是抛出一个HUnitFailure 异常表示失败测试并抛出Result 异常(似乎?)表示测试成功。哪个(几乎)符合要求。我觉得奇怪的是没有Example a =&gt; Example (IO a) 实例,甚至没有Example (IO Result) - 似乎这些对你有用。也许您应该尝试自己编写它们?

标签: haskell haskell-pipes hspec


【解决方案1】:

您可以使用temporary包的功能来创建包含预期数据的临时文件,然后测试管道是否正确读取数据。

顺便说一句,您的 Pipe 正在使用执行惰性 I/O 的 readFile。惰性 I/O 和管道之类的流库不能很好地混合,实际上后者主要是作为前者的替代品存在的!

也许您应该改用执行严格 I/O 的函数,例如 openFilegetLine

严格 I/O 的一个烦恼是它迫使您更仔细地考虑资源分配。如何确保每个文件句柄在最后关闭,或者在出错的情况下?实现此目的的一种可能方法是在 ResourceT IO monad 中工作,而不是直接在 IO 中工作。

【讨论】:

  • 我的代码没有做任何与 IO 类似的事情(实际上是数据库调用等)。此问题中的 IO 概念仅用于演示目的,根本不是问题的意图。我想知道是否以及如何使用 HSpec 测试我的管道代码。
【解决方案2】:

诀窍是使用来自 Pipes ListT monad 转换器的 toListM

import Pipes
import qualified Pipes.Prelude as P
import Test.Hspec

data Person = Person String Int | Unknown deriving (Show, Eq)
data Classification = Friend | Foe | Undecided deriving (Show, Eq)

classify :: Pipe Person (Person, Classification) IO ()
classify = do
  p@(Person name _) <- await
  case name of 
    "Alex" -> yield (p, Friend)
    "Bob" -> yield (p, Foe)
    _ -> yield (p, Undecided)

测试,使用 ListT 转换器将管道转换为 ListT 并使用 HSpec 断言:

main = hspec $ do
  describe "classify" $ do
    it "correctly finds friends" $ do
      [(p, cl)] <- P.toListM $ each [Person "Alex" 31] >-> classify
      p `shouldBe` (Person "Alex" 31)
      cl `shouldBe` Friend

注意,您不必使用each,这可以是一个简单的生产者,调用yield

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-28
    相关资源
    最近更新 更多