恥ずかしながら。ごく最近までHaskellにUnitTestのライブラリがあるのを知りませんでした。恥ずかしい。
調べてみたところ、思ったよりも簡単にUnitTestを書けることがわかったので、ちょっとまとめてみました。
間違いありましたらご指摘いただけるとたいへんありがたいです。
詳細についてはリファレンスをあたってみてください。
HUnitのインポート
HUnitを使うにはTest.HUnitモジュールをインポートします。
import Test.HUnit
GHCiで利用するばあい。
$ ghci Prelude> :m Test.HUnit Prelude Test.HUnit>
アサーション
アサーションは基本的に、常に失敗、Falseのとき失敗、不一致のとき失敗、の3種類です。それぞれ通常の関数での記法と演算子での記法があります。
assertFailure message |
常に失敗する。messageを表示する |
assertBool message cond |
condがFalse のばあい失敗する。失敗した場合messageを表示する |
assertEqual message expected actual |
expectedとactualが等しくないばあい失敗する。失敗した場合messageを表示する |
assertString message |
messageが空文字列("" )でないばあい失敗する。失敗した場合messageを表示する |
expected @=? actual |
expectedとactualが等しくないばあい失敗する。assertEqual "" expected actual と同じ |
actual @?= expected |
expectedとactualが等しくないばあい失敗する。assertEqual "" expected actual と同じ |
cond @? message |
condがFalse のばあい失敗する。assertBool "" cond と同じ |
assertFailure
とassertString
はどちらも単に失敗を発生させるアサーションですが、assertString
は引数の文字列が空のばあい失敗を発生させません。
Prelude Test.HUnit> assertFailure "hoge" *** Exception: HUnitFailure "hoge" Prelude Test.HUnit> assertFailure "" *** Exception: HUnitFailure "" Prelude Test.HUnit> assertString "hoge" *** Exception: HUnitFailure "hoge" Prelude Test.HUnit> assertString "" Prelude Test.HUnit>
ちなみに。アサーションはすぐに評価されるので、上記のようにGHCiで簡単に評価結果を確認することができます。
アサーションの型Assertion
はIO ()
の別名なので、複数のアサーションをdo記法でまとめることができます。
assertHoge = do 10 @=? sum [1,2,3,4] 24 @=? product [1,2,3,4] "hoge" @=? "HOGE"
テストケース
アサーションからテストケースを生成します。テストケースを生成しただけでは評価はされず、別途テストの実行が必要になります。
TestCase
、TestLabel
、TestList
がテストケースのコンストラクタです。アサーションと同じように通常の関数での記法と演算子での記法があります。またアサーション単体、アサーションのリスト、テストケースのリストなどからテストケースを生成するtest
というユーティリティな関数があります。
TestCase assertion |
アサーションからテストケースを生成する |
TestLabel label test |
既存のテストケースにラベルを追加したテストケースを生成する |
TestList tests |
テストケースのリストから(単一の)テストケースを生成する |
expected ~=? actual |
TestCase $ expected ~=? actual と同じ |
actua ~?= expected |
TestCase $ actua ~?= expected と同じ |
cond ~? message |
TestCase $ cond @? message と同じ |
label ~: test |
TestLabel label test と同じ |
test testables |
Testable のインスタンスからテストケースを生成する |
次のふたつは同じ意味になります。
TestLabel "hoge" $ TestCase $ assertEqual "" 1 2
"hoge" ~: 1 ~=? 2
テストの実行
テストの実行にはrunTestTT
関数を使うと簡単です。ちなみに“TT”の意味は“Text-based reporting to the Terminal”だそうです。
runTestTT $ TestLabel "hoge" $ TestCase $ assertEqual "" 1 2
上記のテストをGHCiで実行すると次のような結果が表示されます。
Prelude Test.HUnit> runTestTT $ TestLabel "hoge" $ TestCase $ assertEqual "" 1 2 ### Failure in: hoge expected: 1 but got: 2 Cases: 1 Tried: 1 Errors: 0 Failures: 1 Counts {cases = 1, tried = 1, errors = 0, failures = 1}
runTestTT
関数は結果としてIO Counts
型の値を返します。Counts
型には4つのInt
型の値、cases
、tried
、errors
、failures
があって、それぞれテストケースの個数、実施した個数、エラーになった個数、失敗した個数が格納されています。ちなみに「エラー」が具体的に何をあらわしているかは、まだ調べきれてないです。
上のテストは演算子を使って次のように書くことができます。
runTestTT $ "hoge" ~: 1 ~=? 2
コンパイルして実行するばあい。runTestTT
の戻り値の型がIO Counts
なのでmain
の型とあいません。本来ならCounts
の値を取り出してレポートを出力するのがよいのでしょうが、手っ取り早くはdo記法でreturn ()
を最後に書けばどうにかなります。
main :: IO () main = do runTestTT $ "hoge" ~: 1 ~=? 2 return ()
これらをふまえて。サンプル。
import Test.HUnit fact n = product [1..n] testFacts = [ "test 1!" ~: 1 ~=? fact 1 , "test 2!" ~: 2 ~=? fact 2 , "test 3!" ~: 6 ~=? fact 3 , "test 4!" ~: 25 ~=? fact 4 , "test 51" ~: 120 ~=? fact 5 ] main::IO () main = do counts <- runTestTT $ TestList testFacts putStrLn $ "Cases : " ++ show (cases counts) putStrLn $ "Tried : " ++ show (tried counts) putStrLn $ "Errors : " ++ show (errors counts) putStrLn $ "Failure : " ++ show (failures counts)
実行結果。
### Failure in: 3:test 4! expected: 25 but got: 24 Cases: 5 Tried: 5 Errors: 0 Failures: 1 Cases : 5 Tried : 5 Errors : 0 Failure : 1