我正在尝试测试一个小函数(或者更确切地说,IO Action),它接受一个命令行参数并将其输出到屏幕上。我原来的(不可测试的)功能是:
-- In Library.hs
module Library where
import System.Environment (getArgs)
run :: IO ()
run = do
args <- getArgs
putStrLn $ head args
在查看了这个关于 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
有效包含 2 个字段的 monad。
getArgs
模拟从中读取的命令行参数列表putStrLn
模拟放置传递给它的内容的字符串。
上面的代码有效,似乎测试了我想要测试的内容。但是,我想知道是否有更好/更清洁/更惯用的测试方法。一方面,我使用相同的状态将东西放入测试(我的假命令行参数),然后从中取出东西(传递给putStrLn
.
有没有更好的方法来做我正在做的事情?我更熟悉在 Javascript 环境中进行模拟,并且我对 Haskell 的了解非常基础(我通过大量试验和错误得出上述解决方案,而不是实际理解)