我试图测试一个小函数(或更确切地说,IO操作),它接受一个命令行参数并将其输出到屏幕。我原来(不可测)函数是:嘲笑IO操作:getArgs和putStrLn
-- In Library.hs
module Library where
import System.Environment (getArgs)
run :: IO()
run = do
args <- getArgs
putStrLn $ head args
看着this answer about mocking后,我想出了一个办法是用一种类约束类型嘲笑getArgs
和putStrLn
。所以上面的函数变为:
-- In Library.hs
module Library where
class Monad m => SystemMonad m where
getArgs :: m [String]
putStrLn :: String -> m()
instance SystemMonad IO where
getArgs = System.Environment.getArgs
putStrLn = Prelude.putStrLn
run :: SystemMonad m => m()
run = do
args <- Library.getArgs
Library.putStrLn $ head args
这Library.
,Prelude.
和System.Environment.
是避免Ambigious Occurence
编译器的投诉。我的测试文件如下所示。
-- In LibrarySpec.hs
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
import Library
import Test.Hspec
import Control.Monad.State
data MockArgsAndResult = MockArgsAndResult [String] String
deriving(Eq, Show)
instance SystemMonad (State MockArgsAndResult) where
getArgs = do
MockArgsAndResult args _ <- get
return args
putStrLn string = do
MockArgsAndResult args _ <- get
put $ MockArgsAndResult args string
return()
main :: IO()
main = hspec $ do
describe "run" $ do
it "passes the first command line argument to putStrLn" $ do
(execState run (MockArgsAndResult ["first", "second"] "")) `shouldBe` (MockArgsAndResult ["first", "second"] "first")
我正在使用State
monad,它有效地包含2个字段。
- 一种命令行参数,其中模拟
getArgs
从 - 读取一个字符串,模拟
putStrLn
把什么传递给它的列表。
上面的代码工程,似乎测试我想要它测试。但是,我想知道是否有一些更好/更清洁/更习惯性的测试方式。一方面,我使用的是相同的状态既把东西到测试(我的假命令行参数),然后拿东西出来的(东西传递给putStrLn
。
是否有更好的办法做我在干什么?我更熟悉Javascript环境中的嘲讽,并且我对Haskell的知识是非常基础的(我通过一些反复试验来达到上述解决方案,而不是真正理解)
我认为你的代码很好/干净/习惯用法。真正的问题是这能解决你的问题吗?您的“模型”是否足以准确地表示您正在建模的实际“文件系统”?如果是这样,那么可能没有必要过时这一点。但是,如果你真的想模拟'Prelude.putStrLn','putStrLn'应该把它的字符串参数附加到状态中的旧字符串,而不是忽略旧字符串。 – user2407038 2014-12-03 00:11:27
@ user2407038追加到旧字符串听起来像个好主意。 – 2014-12-03 06:25:35