質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Haskell

Haskellは高い機能性をもった関数型プログラミング言語で、他の手続き型プログラミング言語では難しいとされている関数でも容易に行うことができます。強い静的型付け、遅延評価などに対応しています。

ユニットテスト

ユニットテストは、システムのテスト手法の一つで、個々のモジュールを対象としたテストの事を指します。対象のモジュールが要求や性能を満たしているか確認する為に実行します。

Q&A

2回答

990閲覧

haskellでIOのUnitテストをしたい

qoopty

総合スコア15

Haskell

Haskellは高い機能性をもった関数型プログラミング言語で、他の手続き型プログラミング言語では難しいとされている関数でも容易に行うことができます。強い静的型付け、遅延評価などに対応しています。

ユニットテスト

ユニットテストは、システムのテスト手法の一つで、個々のモジュールを対象としたテストの事を指します。対象のモジュールが要求や性能を満たしているか確認する為に実行します。

0グッド

1クリップ

投稿2021/05/11 02:41

前提・実現したいこと

標準出力、標準入力のテストをしたい
実際にはこの問題
のテストを作りたいです。

IOテストの現在の標準みたいなのがあればそれがしりたいです。

試したこと

1.Haskellで副作用をモックしてテストを書くを参考に書いてみたけど動かず
2.Haskell で書いた Web サービスにおける IO 部分の自動テストをもとに実装をしようと思ったが理解できず実装できなかった。

補足情報(FW/ツールのバージョンなど)

windows10

$stack --version
Version 2.5.1, Git revision d6ab861544918185236cf826cb2028abb266d6d5 x86_64 hpack-0.33.0

stack.yaml

yaml

1resolver: 2 url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/17/10.yaml

サンプルコード(コンパイルエラー)

haskell

1{-# LANGUAGE GeneralizedNewtypeDeriving #-} 2import Control.Monad 3import Control.Monad.Writer 4import Control.Monad.Reader 5import Control.Monad.Identity 6import System.IO 7 8prettyPrint :: (Monad m, MonadStdout m) => String -> m () 9prettyPrint ss= print ss 10 11class MonadStdout m where 12 putStrLnM:: String -> m () 13 14instance MonadStdout IO where 15 putStrLnM = putStrLn 16 17data Settings = Settings -- whatever you like 18newtype MockT m a = MockT (WriterT String (ReaderT Settings m) a) 19 deriving (Functor, Applicative, Monad) 20 21runMockT :: MockT m a -> Settings -> m (a, String) 22runMockT (MockT w) = runReaderT $ runWriterT w 23 24runMock :: MockT Identity a -> Settings -> (a, String) 25runMock = fmap runIdentity . runMockT 26 27instance MonadTrans MockT where 28 lift = MockT . lift . lift 29 30instance Monad m => MonadStdout (MockT m) where 31 putStrLnM s = MockT $ tell $ s <> "\n" 32 33spec :: Monad m => m ((), String) 34spec = flip runMockT Settings $ 35 prettyPrint "something1" 36 37main = spec 38

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

残念ながら、「IOテストの現在の標準」にあたるものはありません。ものすごくいろいろな方法が作られています。

ひとまず、挙げていただいたコードを直すとすると、 prettyPrint関数を例えば????のように直してください:

hs

1prettyPrint :: (Monad m, MonadStdout m) => String -> m () 2prettyPrint ss= putStrLnM $ show ss

print はあくまでも入出力のための関数であり、テストでは使いにくいので、テストで使わないために他の関数で置き換えてしまおう、というのが「試したこと」に挙げたページでのアプローチです。なのでここではprint関数を使わず、MonadStdoutのメソッド(putStrLnM)を使って定義する必要があります。

このように、「他の関数で置き換えてしまう」アプローチは本当にIOを使うわけではないので実行効率はいいものの、微妙に振る舞いが異なる場合がある、というのが弱点です。

Haskellで(実はHaskell以外でも)IOが絡んだテストをするときには、しばしばこのアプローチがとられます。調べて出てくる大半の方法は突き詰めればこの「他の関数で置き換えてしまおう」という作戦です。
(main-testerは敢えてそこで本物のIOを使うよう推奨することで、より実際に近いテストにしています)

投稿2021/05/15 08:53

igrep

総合スコア433

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

qoopty

2021/05/16 04:59

>Haskellで(実はHaskell以外でも)IOが絡んだテストをするときには、しばしばこのアプローチがとられます。 一般的にmockって言われてるものと同じということでいいでしょうか? pythonは比較的簡単にできますよね。 javaは何らかのframeworkを使ってできた記憶があります。(ほかの言語はあまりしらない) ほんとの結合テストにするのか ほかの関数で置き換えるのか どちらかのアプローチしかなさそうですね。 #IOテストはずっと課題だと思うんですが、難しいのかな
igrep

2021/05/16 09:38

そうです。Haskellの場合良くも悪くも、あらかじめ実装者がmockできる余地を作っておかなければmockできないという特性があるので、難易度は他の言語と比べて上がってしまいます。変に書き換えられないという意味で良いところでもあるのですが。
guest

0

main-tester パッケージを使えば標準入出力のテストができるようになります。

main-tester に関する詳しい情報は Haskell-jp のブログをご覧ください。

まずライブラリーとして main を定義します。下記のコードでは仮に標準入力を標準出力にエコーしています。ただし AtCoder に提出するときはモジュール名を Main にしてください。

lang

1-- UnitTestIO.hs 2 3module UnitTestIO (main) where 4 5main :: IO () 6main = interact id

テストは次のように記述します。

lang

1-- UnitTestIOSpec 2 3{-# LANGUAGE OverloadedStrings #-} 4 5import qualified Data.ByteString as B 6import System.Exit (ExitCode (ExitSuccess)) 7import Test.Hspec (hspec, it, shouldBe, shouldSatisfy) 8import Test.Main (captureProcessResult, prExitCode, prStderr, 9 prStdout, withStdin) 10import qualified UnitTestIO 11 12main = 13 hspec $ do 14 it "hello → hello" $ do 15 result <- withStdin "hello" $ captureProcessResult UnitTestIO.main 16 prExitCode result `shouldBe` ExitSuccess 17 prStderr result `shouldSatisfy` B.null 18 prStdout result `shouldBe` "hello"

プロジェクトファイルも含めた完全なコードは Gist に掲載します。

投稿2021/05/11 08:31

編集2021/05/12 04:22
kakkun61

総合スコア285

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

qoopty

2021/05/12 08:14

ありがとうございます。 これ事前に入力文字列をすべて与える必要があると思うのですが、 動的に入力を与えることってできないでしょうか? 最初の出力を受けて、それに応じて次の入力の文字列が変わるようなことをしたいのです。
kakkun61

2021/05/14 03:35

既存ライブラリーでは解決できなさそうなケースですね。ちょっと考えてみます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問