Mic*_*mza 5 monads haskell unit-testing mocking hunit
我正在尝试测试一个带有命令行参数并将其输出到屏幕的小功能(或更确切地说是IO Action)。我原来的(无法调试的)功能是:
-- In Library.hs
module Library where
import System.Environment (getArgs)
run :: IO ()
run = do
args <- getArgs
putStrLn $ head args
Run Code Online (Sandbox Code Playgroud)
细算这个答案约嘲讽,我想出了一个办法来嘲笑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
Run Code Online (Sandbox Code Playgroud)
这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")
Run Code Online (Sandbox Code Playgroud)
我正在使用State有效包含2个字段的monad。
getArgs从中读取命令行参数的列表putStrLn放置传递给它的内容的字符串。上面的代码有效,并且似乎可以测试我想要测试的东西。但是,我想知道是否有更好/更清洁/更惯用的方式进行测试。一方面,我使用相同的状态将东西放入测试中(我的假命令行参数),然后从中取出东西(传递给的东西)putStrLn。
有做我正在做的更好的方法吗?我对Javascript环境中的模拟更加熟悉,并且我对Haskell的了解非常基础(我通过反复试验而不是实际理解得出了上述解决方案)
更好的方法是通过将计算的核心分离到纯函数中来避免提供getArgs和的模拟版本。putStrLn
考虑这个例子:
main = do
args <- getArgs
let n = length $ filter (\w -> length w < 5) args
putStrLn $ "Number of small words: " ++ show n
Run Code Online (Sandbox Code Playgroud)
可以说,计算的核心是计算小单词的数量,这是 type 的纯函数[String] -> Int。这建议我们应该像这样重构程序:
main = do
args <- getArgs
let n = countSmallWords args
putStrLn $ "Number of small words: " ++ show n
countSmallWords :: [String] -> Int
countSmallWords ws = ...
Run Code Online (Sandbox Code Playgroud)
现在我们只需测试countSmallWords,这很容易,因为它是纯函数。