正如 Alexander Poluektov 已经指出的那样,您尝试测试的代码可以很容易地分为纯部分和不纯部分。
尽管如此,我认为知道如何在 haskell 中测试这些不纯函数是件好事。
在haskell 中进行测试的常用方法是使用quickcheck,这也是我倾向于用于不纯代码的方法。
这是一个示例,说明您如何实现您正在尝试做的事情,它为您提供了一种 mock 行为 * :
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)
现在提供一个替代函数(Testing against a model):
numAlternative :: FilePath -> IO Integer
numAlternative p = bracket (openFile p ReadMode) hClose hFileSize
为测试环境提供一个任意实例:
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
针对模型的属性测试(使用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 为您创建一些随机文件并针对模型函数测试您的实现。
$ quickCheck prop_charsInFile
+++ OK, passed 100 tests.
当然,您也可以根据您的用例测试其他一些属性。
* 请注意我对模拟行为一词的用法:
面向对象意义上的 mock 术语在这里可能不是最好的。但是模拟背后的意图是什么?
它可以让您测试需要访问资源的代码,该资源通常是
通过将提供此类资源的责任转移到快速检查上,为被测代码提供可在测试运行后验证的环境突然变得可行。
Martin Fowler 在article about mocks 中很好地描述了这一点:
“Mocks 是...预先编程的对象,这些对象形成了它们期望接收的调用的规范。”
对于快速检查设置,我会说作为输入生成的文件是“预编程的”,以便我们知道它们的大小(== 期望)。然后根据我们的规范(== 属性)对它们进行验证。