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

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

问题描述

现在正在通过Real World Haskell进行工作。以下是书中很早练习的解答: 4)计算文件中字符的数量
numCharactersInFile :: FilePath - > IO Int
numCharactersInFile fileName = do
contents< - readFile fileName
return(length contents)

我的问题是:你会如何测试这个功能?有没有办法做一个模拟输入,而不是实际上需要与文件系统交互来测试它? Haskell把重点放在纯函数上,我不得不想象这很容易做到。

正如Alexander Poluektov已经指出的那样, 解决方案

你试图测试的代码可以很容易地分为纯粹和不纯的部分。
然而,我认为知道如何在haskell中测试这些不纯的函数是很好的。

在haskell中测试的常用方法是使用 quickcheck ,这就是我也倾向于使用不纯代码。



这是一个你如何实现你正在尝试做的事情,这会给你带来一种 mock 行为 *

<$ p $ $ b $ import import Test.QuickCheck
import Test.QuickCheck.Monadic(monadicIO,run,assert)
import System.Directory(removeFile,getTemporaryDirectory)
import System.IO
import Control.Exception(最后,括号)

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

现在提供一个替代函数根据模型测试)

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

提供一个任意实例对于测试环境:

$ p $ data TestFile = TestFile字符串派生(Eq,Ord,Show)
实例任意TestFile其中
任意=做
n< - 选择(0,2000)
testString< - vectorOf n $ elements ['a'..'z']
return $ TestFile testString

对模型进行属性测试(使用 quickcheck for monadic code ):

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



和一个小帮手函数:

  createTmpFile :: String  - > (FilePath→>句柄→> IO a)→> IO 
createTmpFile内容func = do
tempdir< - catch getTemporaryDirectory(\_ - > return。)
(tempfile,temph)< - openTempFile tempdir
hPutStr temph content
hFlush temph
hClose temph
finally(func tempfile temph)
(removeFile tempfile)

这将让quickCheck为您创建一些随机文件,并根据模型函数测试您的实现。

  $ quickCheck prop_charsInFile 
+++好的,通过了100次测试。

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






* 请注意我对模拟行为一词的使用情况:

在面向对象的意义上,术语 mock 在这里可能不是最好的。但是,模拟背后的意图是什么?

让我们来测试需要访问资源的代码,这些资源通常是


  • 或者在测试时间不可用

  • 或者不容易控制,因此不容易验证。


$ b $通过将提供这种资源的责任转移到快速检查,突然变得可行,为测试后的代码提供一个可以在测试运行后验证的环境。

Martin Fowler很好地描述了这一点在有关mocks的文章

Mocks are ...预先编程有预期的对象,这些预期形成了预期要接收的调用的规范。

对于快速检查设置,我会说生成为输入的文件是pre编程,以便我们知道它们的大小(==期望)。然后他们根据我们的规范(==属性)进行验证。


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)

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.

解决方案

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

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

And a little helper function:

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)

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.


* 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

  • either not available at testing time
  • or is not easily controllable and thus not easy to verify.

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的Haskell中测试函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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