在 Haskell 中测试执行 IO 的函数 [英] Testing functions in Haskell that do IO

查看:35
本文介绍了在 Haskell 中测试执行 IO 的函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

现在正在使用 Real World Haskell.这是书中一个非常早的练习的解决方案:

Working through Real World Haskell right now. Here's a solution to a very early exercise in the book:

-- | 4) Counts the number of characters in a file
numCharactersInFile :: FilePath -> IO Int
numCharactersInFile fileName = do
    contents <- readFile fileName
    return (length contents)

我的问题是:您将如何测试此功能?有没有办法进行模拟"输入而不是实际需要与文件系统交互来测试它?Haskell 如此强调纯函数,我不得不想象这很容易做到.

My question is: How would you test this function? Is there a way to make a "mock" input instead of actually needing to interact with the file system to test it out? Haskell places such an emphasis on pure functions that I have to imagine that this is easy to do.

推荐答案

正如 Alexander Poluektov 已经指出的那样,您尝试测试的代码很容易被分为纯部分和不纯部分.尽管如此,我认为知道如何在 haskell 中测试这种不纯的函数是件好事.
在 haskell 中进行测试的常用方法是使用 quickcheck,这也是我也倾向于用于不纯的代码.

As Alexander Poluektov already pointed out, the code you are trying to test can easily be separated into a pure and an impure part. Nevertheless I think it is good to know how to test such impure functions in haskell.
The usual approach to testing in haskell is to use quickcheck and that's what I also tend to use for impure code.

这里有一个例子,说明你如何实现你正在尝试做的事情,它会给你一种模拟行为*:

Here is an example of how you might achieve what you are trying to do which gives you kind of a mock behavior * :

import Test.QuickCheck
import Test.QuickCheck.Monadic(monadicIO,run,assert)
import System.Directory(removeFile,getTemporaryDirectory)
import System.IO
import Control.Exception(finally,bracket)

numCharactersInFile :: FilePath -> IO Int
numCharactersInFile fileName = do
    contents <- readFile fileName
    return (length contents)

现在提供一个替代函数(针对模型进行测试):

Now provide an alternative function (Testing against a model):

numAlternative ::  FilePath -> IO Integer
numAlternative p = bracket (openFile p ReadMode) hClose hFileSize

为测试环境提供任意实例:

Provide an Arbitrary instance for the test environment:

data TestFile = TestFile String deriving (Eq,Ord,Show)
instance Arbitrary TestFile where
  arbitrary = do
    n <- choose (0,2000)
    testString <- vectorOf n $ elements ['a'..'z'] 
    return $ TestFile testString

针对模型的属性测试(使用 快速检查 monadic 代码):

Property testing against the model (using quickcheck for monadic code):

prop_charsInFile (TestFile string) = 
  length string > 0 ==> monadicIO $ do
    (res,alternative) <- run $ createTmpFile string $
      p h -> do
          alternative <- numAlternative p
          testRes <- numCharactersInFile p
          return (testRes,alternative)
    assert $ res == fromInteger alternative

还有一个小辅助函数:

createTmpFile :: String -> (FilePath -> Handle -> IO a) -> IO a
createTmpFile content func = do
      tempdir <- catch getTemporaryDirectory (\_ -> return ".")
      (tempfile, temph) <- openTempFile tempdir ""
      hPutStr temph content
      hFlush temph
      hClose temph
      finally (func tempfile temph) 
              (removeFile tempfile)

这将使 quickCheck 为您创建一些随机文件,并针对模型函数测试您的实现.

This will let quickCheck create some random files for you and test your implementation against a model function.

$ quickCheck prop_charsInFile 
+++ OK, passed 100 tests.

当然,您也可以根据您的用例测试其他一些属性.

Of course you could also test some other properties depending on your usecase.

* 请注意我对模拟行为这个词的用法:
面向对象意义上的 mock 术语在这里可能不是最好的.但是模拟背后的意图是什么?
它让你测试需要访问资源的代码,通常是

* Note about the my usage of the term mock behavior:
The term mock in the object oriented sense is perhaps not the best here. But what is the intention behind a mock?
It let's you test code that needs access to a resource that usually is

  • 在测试时不可用
  • 或者不容易控制,因此不容易验证.

通过将提供此类资源的责任转移到快速检查,为被测代码提供一个可以在测试运行后进行验证的环境突然变得可行.
Martin Fowler 在关于模拟的文章中很好地描述了这一点:
Mocks 是......预编程的对象,这些对象形成了他们期望接收的调用的规范."
对于快速检查设置,我会说作为输入生成的文件是预编程的",这样我们就知道它们的大小(== 期望值).然后根据我们的规范(== 属性)验证它们.

By shifting the responsibility of providing such a resource to quickcheck, it suddenly becomes feasible to provide an environment for the code under test that can be verified after a test run.
Martin Fowler describes this nicely in an article about mocks :
"Mocks are ... objects pre-programmed with expectations which form a specification of the calls they are expected to receive."
For the quickcheck setup I'd say that files generated as input are "pre-programmed" such that we know about their size (== expectation). And then they are verified against our specification (== property).

这篇关于在 Haskell 中测试执行 IO 的函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆