我正在尝试测试一个小函数(或者更确切地说,IO Action),它接受命令行参数并将其输出到屏幕。我原来的(不可测试的)功能是:
-- 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")
我正在使用有效包含2个字段的State
monad。
getArgs
从putStrLn
放置传递给它的内容的字符串。以上代码有效,似乎测试了我想要测试的内容。但是,我想知道是否有一些更好/更清洁/更惯用的测试方法。首先,我使用相同的状态将两个东西放入测试(我的假命令行参数),然后从中获取东西(传递给putStrLn
的内容。
有没有更好的方法来做我正在做的事情?我对Javascript环境中的模拟比较熟悉,而且我对Haskell的了解非常基础(我通过一些试验和错误而不是实际的理解来达到上述解决方案)
答案 0 :(得分:1)
更好的方法是避免需要通过将计算的核心分离为纯函数来提供getArgs
和putStrLn
的模拟版本。
考虑这个例子:
main = do
args <- getArgs
let n = length $ filter (\w -> length w < 5) args
putStrLn $ "Number of small words: " ++ show n
可以说,计算的核心是计算小词的数量,这是[String] -> Int
类型的纯函数。这表明我们应该重构这个程序:
main = do
args <- getArgs
let n = countSmallWords args
putStrLn $ "Number of small words: " ++ show n
countSmallWords :: [String] -> Int
countSmallWords ws = ...
现在我们只测试countSmallWords
,这很简单,因为它是纯函数。