2014-12-02 2 views
4

명령 줄 인수를 사용하여 화면에 출력하는 작은 함수 (또는 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보고 후, 나는 타입 클래스 제약 형식을 사용하여 getArgsputStrLn을 조롱 수있는 방법을 마련했다. 그래서 위의 함수가된다 :

-- 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 모나드를 사용하고 있습니다.

  1. 모의 getArgs이 모의 putStrLn가 전달 된 것을두고 있음을
  2. 에서 문자열을 읽는 명령 행 인수에 대한 목록입니다.

위의 코드는 작동하며 내가 테스트하고 싶은 것을 테스트하는 것으로 보입니다. 그러나,이 더 나은/청소기/더 많은 관용적 인 방법이 있는지 궁금하네요. 한 가지는, 같은 상태를 사용하여 테스트에 물건을 넣고 (내 가짜 명령 행 인수) putStrLn에 전달 된 내용입니다.

더 좋은 방법이 있습니까? 내가하고있는 일을하고 있니? 나는 Javascript 환경에서 조롱하는 것에 더 익숙하다. 하스켈에 대한 나의 지식은 꽤 기초적이다. (나는 실제적인 이해가 아니라 시행 착오의 공정한 비트에 의해 위의 해결책에 도달했다.)

+1

나는 당신의 코드가 좋은/깨끗한/관용적이라고 생각합니다. 진짜 질문은 이것이 당신의 문제를 해결하는 것인가? "모델"은 모델링중인 실제 "파일 시스템"을 정확하게 나타 내기에 충분한가요? 그렇다면 아마도 이것을 과장 할 필요가 없을 것입니다. 그러나'PutStrLn'은'Prelude.putStrLn'을 실제로 시뮬레이트하고 싶다면 아마도 이전 문자열을 무시하는 대신에 문자열 인수를 상태의 이전 문자열에 추가해야합니다. – user2407038

+0

@ user2407038 오래된 문자열에 추가하는 것은 좋은 생각입니다. –

답변

1

더 좋은 방법은 순수 함수로 계산의 마음을 분리하여 getArgsputStrLn의 모의 버전을 제공 할 필요 방지하는 것입니다.

공동 이 예 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

어 ... 내 원래의 질문에 하나, 그리고 귀하의 대답에 하나 꽤 좋은 예제처럼 보인다! 아마도 내가 네가 요구하는 것을 이해하지 못했을거야? –