2014-12-02 3 views
4

Я пытаюсь протестировать небольшую функцию (точнее, IO Action), которая принимает аргумент командной строки и выводит ее на экран. Мой оригинал (непроверяема) функция:Mocking IO Actions: 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 монаду, которая эффективно содержит 2 поля.

  1. список для аргументов командной строки, где издеваться getArgs читает из
  2. Строка, макет putStrLn ставит то, что было передано ему.

Приведенный выше код работает и, кажется, проверяет, что я хочу проверить. Тем не менее, мне интересно, есть ли какой-то лучший/более чистый/более идиоматический способ тестирования этого. Во-первых, я использую одно и то же состояние, чтобы оба помещали материал в тест (мои аргументы в поддельной командной строке), а затем извлекали из него все (что было передано putStrLn.

Есть ли лучший способ что я делаю? Я больше знаком с издевательством в среде Javascript, и мои знания о Haskell довольно просты (я пришел к вышеуказанному решению с честной пробной ошибкой, а не с реальным пониманием)

+1

Я думаю, что ваш код хороший/чистый/идиоматический. Реальные вопросы - это решение вашей проблемы? Является ли ваша «модель» достаточной для точного представления фактической «файловой системы», которую вы моделируете? Если это так, то, вероятно, нет необходимости переубеждать это. Но 'putStrLn' должен, вероятно, добавить свой аргумент строки в старую строку в состоянии, вместо игнорирования старой строки, если вы действительно хотите симулировать' Prelude.putStrLn'. – user2407038

+0

@ user2407038 Добавление к старой строке звучит как хорошая идея. –

ответ

1

лучший способ, чтобы избежать необходимости предоставлять ложные версии getArgs и putStrLn путем выделения из сердца вычисления в чистой функции.

Co nsider этом примере:

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, и это легко, потому что это чистая функция.

+0

Да, разделение на чистые функции кажется хорошим, но всегда будут какие-то действия IO (/ функции, возвращающие действия IO), которые, как представляется, нуждаются в тестировании, по крайней мере, что они передают результат чистых функций. –

+0

Придумайте пример, и мы можем изучить способы его организации, чтобы упростить тестирование. – ErikR

+0

Er ... Тот, что в моем первоначальном вопросе, и тот, в вашем ответе, выглядит неплохо! Возможно, я не понимаю, что вы запрашиваете? –

Смежные вопросы