このエントリは、 Haskell入門の執筆 の際に、 ページ数の都合で載せられなかった記事に加筆修正をしたものです。1.4節に載せる予定だった内容です。
このエントリの内容を試すには、 Stackをインストール し、 /tmp
ディレクトリなどで以下のコマンドを実行してプロジェクトを作成してください。
$ stack new myproject
$ cd myproject/
初めての開発: 単体テストを書く
開発フローを体験するため、未実装だった単体テストを作ってみましょう。開発をするときにはREPLを使うのも便利ですが、ここでは --file-watch
オプションを使います。--file-watch
オプションを stack build
へ指定すると、プログラムに変更があるかを監視し、変更があった場合は自動的にビルドをしてくれます。さらに、 build
に--test
オプションを渡すと stack test
と同等の機能になります。このようにしてstack build
を常時走らせておくと、プログラムにエラーがあった時にすぐに見つけることができます。
$ stack build --test --file-watch
.. 略 ..
myproject-0.1.0.0: test (suite: myproject-test)
Progress: 1/2Test suite not yet implemented
Completed 2 action(s).
ExitSuccess
Type help for available commands. Press enter to force a rebuild.
単体テストを書くために、パッケージをいくつか使います。利用したresolverに登録されているパッケージを使うのが一番トラブルが少ないので、その中から使えそうなパッケージを見繕います。パッケージの一覧は Stackage のサイトで確認できます。最新のLTSを使っている場合は、 https://www.stackage.org/lts で一覧を見ることができますので、ここからパッケージを選びます。今回は HUnit
と test-framework
とtest-framework-hunit
、 そして silently
を利用してみましょう。パッケージを利用するには、 .cabal
ファイルに依存関係を追加します。 myproject.cabal
のテストシートのビルド設定に、パッケージを4つ追加してください。
test-suite myproject-test
.. 省略 ..
build-depends: base
, myproject
-- 以下を追加
, HUnit
, silently
, test-framework
, test-framework-hunit
.cabal
ファイルを変更すると、 --file-watch
が変更を捉えて自動的にビルドしてくれます。追加したパッケージは、自動的にHackageからダウンロードされ、ビルドされます。
.. 略 ..
Type help for available commands. Press enter to force a rebuild.
myproject-0.1.0.0: unregistering (missing dependencies: HUnit, silently, test-framework, test-framework-hunit)
hostname-1.0: using precompiled package
mtl-2.2.1: using precompiled package
.. 略 ..
Completed 18 action(s).
ExitSuccess
Type help for available commands. Press enter to force a rebuild.
追加したパッケージを使って単体テストを書きます。自分のプログラムでパッケージを利用するには、パッケージに含まれる各モジュールを import
文で使うことを宣言します。ここでは、test/Spec.hs
を以下の内容に書き換えてください。ここではコードの解説はしませんが、プログラミングの経験がある方であればなんとなく何をやっているかは想像できるのではないでしょうか。
module Main (main) where
import qualified Lib as Lib
import qualified System.IO.Silently as Silently
import qualified Test.Framework as Test
import qualified Test.Framework.Providers.HUnit as Test
import Test.HUnit ((~=?))
main :: IO ()
main = do
captured <- Silently.capture_ Lib.someFunc
Test.defaultMain (Test.hUnitTestToTests ("someFunc" ~=? captured))
ファイルを編集すると、 --file-watch
が自動的に単体テストを走らせてくれます。
.. 略 ..
myproject-0.1.0.0: test (suite: myproject-test)
: [Failed]1/2
expected: "someFunc"
but got: "someFunc\n"
Test Cases Total
Passed 0 0
Failed 1 1
Total 1 1
Completed 2 action(s).
Test suite failure for package myproject-0.1.0.0
myproject-test: exited with: ExitFailure 1
Logs printed to console
Type help for available commands. Press enter to force a rebuild.
残念ながらテストが失敗してしまいました。 Lib.hs
で利用している putStrLn
は改行コードも出力するようですね。テストの方が誤ってますので、以下のようにテストを直します。
-- .. 省略 ..
Test.defaultMain (Test.hUnitTestToTests ("someFunc\n" ~=? captured))
これでまたテストが通るようになりました。
.. 略 ..
myproject-0.1.0.0: test (suite: myproject-test)
: [OK]ss: 1/2
Test Cases Total
Passed 1 1
Failed 0 0
Total 1 1
Completed 2 action(s).
ExitSuccess
Type help for available commands. Press enter to force a rebuild.