前回はひとまずスペル修正プログラムが完成して、コマンドラインから修正したい単語の引数を与えるとその修正した単語と修正候補のトップ5が表示されるようにしてみた。
今回はスペル修正プログラムはどう書くかに従って、このスペル修正プログラムの評価ができるようなコードを書いていきたい。ユニットテストのフレームワークを使って修正の正解率に対してテストを書くようなイメージでいる。
ユニットテストをIntelliJ + Haskell環境でうまく動くようにする
今回はHUnitを使って、IntelliJ + Haskell環境でユニットテストが動くようにしたい。 まずは、HUnitを使えるようにcabalでパッケージをインストールする。
cabal install hunit cabal install test-framework cabal install test-framework-hunit
alexがインストール出来ない場合はgccのバージョンが不適合な場合があるのでcabal install --reinstall alex
して再インストールすると直ることがある。
テストが出来るようにしていく:テスト用のダミーコードをつくる
基本的には
cabal test
とすると
Re-configuring with test suites enabled. If this fails, please run configure manually. Resolving dependencies... Configuring spllerE-1.0... Building spllerE-1.0...
という風に本来必要なcabal configure --enable-test
とcabal build
をやってからテストの実行をやってくれる、が
Package has no test suites.
と言われてしまう。当然テストに必要な物は何も足していないので、何もテストしない。これを解決していく。 必要なことは
- srcディレクトリの下にtestディレクトリを作ってその中にテスト用のバイナリのMain.hsを作る。
- そのMain.hsでテストするために.cabalにテスト用のエントリを足す
の2つ。
まずはスペル修正とは関係ないダミーのプログラムを書いてsrc/test/Main.hsに入れよう。
testの下にMainという名前のモジュールを追加する。module test.Main where
となるかもしれないが、test.は不要。以下の様なダミーのテストを書く。
module Main where import Test.HUnit import Test.Framework import Test.Framework.Providers.HUnit main = do defaultMain $ hUnitTestToTests $ TestLabel "hoge" $ TestCase assertOne assertOne = do 10 @=? sum [1,2,3,4] 24 @=? product [1,2,3,4] "hoge" @=? "HOGE"
これがテスト。JUnitに似た雰囲気を感じ取ってもらえると思う。つまり、
- HUnitのファイルは単なるモジュール。普通にプログラムとしてコンパイルできる。
- 書き方が関数型言語っぽいが、IOモナドを使うことで順番に実行する手続き型言語の雰囲気を醸し出す
- assertOneという関数を作って、その中で複数のことをassertしている。Assertは関数名としてassertEqualとかJavaっぽい関数で使うこともできるし、中置演算子で行うこともできる。
内容は後で深く追うとして、この内容でとりあえずセーブ。つぎはcabalファイルを編集してこれをビルドできるようにする。
テストが出来るようにしていく:.cabalファイルにテスト用のエントリを追加する
テストをするために、つまりcabal test
で走らせるためにはTest-Suiteエントリが必要。内容は以下の感じ
Test-Suite Test type: exitcode-stdio-1.0 main-is: Main.hs hs-source-dirs: src/test build-depends: base,HUnit,test-framework,test-framework-hunit ghc-options: -Wall -O2
executableと雰囲気が似ている。typeというのにexitcode-stdio-1.0と書いてあって、このexitcode-stdio-1.0はプログラムの終了コードでテストの成否をcabalに伝えるtypeである、ということを指示している。他はexecutableと特に変わらない。コードで使うパッケージはHUnitのものとtest-frame-workというこのexitcode-stdio-1.0で終了コードをきちんと吐き出すために必要なフレームワークを追加した。
これでcabal testをしてみよう。
$ cabal build Building spllerE-1.0... Preprocessing executable 'spllerE' for spllerE-1.0... Preprocessing test suite 'Test' for spllerE-1.0... src/test/Main.hs:3:12: Could not find module `Test.Framework' It is a member of the hidden package `test-framework-0.8.0.3'. Perhaps you need to add `test-framework' to the build-depends in your .cabal file. Use -v to see a list of the files searched for. Kodamas-MacBook-Air:~/Documents/OneDrive/IdeaProject/spllerE$ cabal test Building spllerE-1.0... Preprocessing executable 'spllerE' for spllerE-1.0... Preprocessing test suite 'Test' for spllerE-1.0... Running 1 test suites... Test suite Test: RUNNING... :hoge: [Failed] expected: "hoge" but got: "HOGE" Test Cases Total Passed 0 0 Failed 1 1 Total 1 1 Test suite Test: FAIL Test suite logged to: dist/test/spllerE-1.0-Test.log 0 of 1 test suites (0 of 1 test cases) passed.
うまく動いているみたい。文字列の比較がわざと間違っているので、これで正解。
ここまで超走りながら来たけど、とりあえずsrc/test/Main.hsにテストを書いていけばcabal test
できるようになった。仕組みは後で追いかけることにする。
IntelliJからユニットテストを走らせたい
コマンドラインからcabal testはだるい。IntelliJのRun configurationを定義してクリック一発でテストできるようにしてみる。
RunメニューのEdit Configurations...から新しいRun configurationをつくる。
中身はこんな感じ:
まずは上のように既存のRun configurationと同じものを作る。executableがTestなところとBefore launchのところが違う。Before launchでcabal testを呼び出すようにしてみた。これだとcabal testした後に、Test自体を実行してテストを実行してしまう。すこし冗長だけど、とりあえずこれで行ってみる。 cabal testをBefore launchするために以下のようなExternal commandをつくってみた:
これで"spllerE test"をRunしてみると(spllerEは前回まで作ってきたspllerと全く同じものの別名だ):
と、cabal testと全く同じものが現れた。成功だ。
というわけで今回はここまで
残念、評価するところまで行かなかった。ただテストフレームワークが手に入ったのでsrc/test/Main.hsをがしがし書いていける。ここに評価に必要ないろいろなものをがしがし書いていきたい。
今回はいろいろなところを参考に以下まで辿り着いた。参考までに:
HaskellのUnitTest、HUnitについて学ぶ - エンジニアのソフトウェア的愛情
本物のプログラマはHaskellを使う - 第57回 機能テストや性能テストをCabalで自動化:ITpro