OUnitでユニットテスト

これまで、ちょこちょこOCamlを触ってみて感じたことなのですが、OCamlコンパイル時の型チェックが非常に強力なので、

  • コンパイルが通るまでが一苦労
  • でも、起動させると期待通り動く

ということが他の言語に比べると多いように思います(コンパイルエラーが取れにくいのは私のショボさではありますが…)。

とはいえ、当然ユニットテストはおろそかにできませんし、何よりこの一年ですっかりtest first厨になってしまった私は「先にテスト書きたい」という欲求を抑えられません。

ちょっと探してみたところ、OUnitというユニットテスト用のパッケージがあるようなので、それを試してみたいと思います。環境は前回同様FreeBSD6.3で、devel/ocaml-ounitというports経由でOUnitをインストールしています。

今回は例として二分木をやってみます。

新人のときのCの練習以来の二分木です。ドキドキします。テストスクリプト(test_tree.ml)はこんな感じ。

open OUnit
open Tree

let setup _ =
  Br(6, Br(3, Br(1, Lf, Lf), Br(2, Lf, Lf)),
  Br(5, Lf, Br(4, Lf, Lf)))

let teardown _ = ()

let test_list_of_tree_pre t =
  let l = list_of_tree_pre t in
    assert_equal l [6; 3; 1; 2; 5; 4]

let test_list_of_tree_in t =
  let l = list_of_tree_in t in
    assert_equal l [1; 3; 2; 6; 5; 4]

let test_list_of_tree_post t =
  let l = list_of_tree_post t in
    assert_equal l [1; 2; 3; 4; 5; 6]

let suite = "Test Tree" >:::
  ["test_list_of_tree_pre" >::(bracket setup test_list_of_tree_pre teardown);
   "test_list_of_tree_in" >::(bracket setup test_list_of_tree_in teardown);
   "test_list_of_tree_post" >::(bracket setup test_list_of_tree_post teardown)]

let _ = run_test_tt_main suite

Treeモジュール内で type 'a tree = Lf | Br of 'a * 'a * 'a tree が定義され、行きがけ順、通りがけ順、帰りがけ順でリストを作成する関数が定義されるつもりで、それらの関数をテストします。そして、以下がサンプル二分木のイメージ図です。

     6
  +--+--+
  |     |
  3     5
+-+-+ +-+
|   | |
1   2 4

ちなみに、OUnitモジュールのbracketという関数はテストのセットアップと後始末をやってくれています。

そしておもむろに

$ ocamlbuild -ocamlc 'ocamlfind c -package oUnit -linkpkg' test_tree.byte 

と叩いてみます。Tree(tree.ml)モジュールが無いので当然コンパイルエラーになります。

なお、ここで使っているocamlbuildはOCaml3.10から標準配備されたビルドツールだそうです。すごい便利なんですが、パッケージ管理ツールであるocamlfindとの連携がすっきりしない…

$ ocamlbuild -pkg oUnit test_tree.tyte 

とか、できると素敵なんですけども。

と、それはさておき。今度はテストに通るべくtree.mlのコーディングを頑張ります。

四苦八苦は省略して、えいっ!

type 'a tree = Lf | Br of 'a * 'a tree * 'a tree

let list_of_tree_pre tree =
  let rec loop t l =
    match t with
    | Lf -> l
    | Br (v, left, right) -> v :: loop left (loop right l) in
    loop tree []

let list_of_tree_in tree =
  let rec loop t l =
    match t with
    | Lf -> l
    | Br (v, left, right) -> loop left (v :: loop right l) in
    loop tree []

let list_of_tree_post tree =
  let rec loop t l =
    match t with
    | Lf -> l
    | Br (v, left, right) -> loop right (loop left l) @ [v] in
    loop tree []


そして、先程のocamlbuildコマンドを叩いて生成されたtest_tree.byteを叩くと

./test_tree.byte
...
Ran: 3 tests in: 0.00 seconds.
OK

やったぁ。

OCamlの場合、コンパイルが通るだけでも達成感があるのですが、その後すぐOUnitも通りやすいので、ダブルの達成感が得られます。達成感マニアにはたまらないかと思います。

あと、まじめな話、CやPerlRubyのように「意図せず、NULLやundefやnilが渡ってきちゃってエラー」という失敗が無い(と言いきっちゃってよいのかしら?)ので、その辺り個人的には凄く嬉しいです。ぬるぽも言わずもがな。

まだまだ、使っているというより触っている程度なので、良く分かっていないところが結構多いんですが(特にファンクターはスルー)、もうしばらく弄ってみたいと思います。