type holyshared = Engineer<mixed>

技術的なことなど色々

OUnitでOCamlライブラリのテストを書く

これはML Advent Calendar 2016の18日目の記事です。

OCamlのテストフレームワークOUnitについて解説します。
また、Oasisの簡単な説明も行います。

プロジェクトのセットアップ

OCamlのプロジェクトでは、だいたいの場合はOasisを使います。
Oasisはビルドの為の支援ツールで、ビルドに必要なMakefileやメタ情報を含んだファイルを設定から自動生成できます。

OasisOpamを使用して簡単にインストールできます。

opam install oasis

インストールが完了したら、設定ファイル_oasisを作成し、必要最低限の情報を記述します。

OASISFormat: 0.4
Name:        example                                        # プロジェクト名
Version:     0.1.0                                          # バージョン
Synopsis:    example                                        # ライブラリの説明
Authors:     Noritaka Horio <holy.shared.design@gmail.com> # 著作者
License:     MIT                                            # ライセンス
Plugins:     META (0.4), StdFiles (0.4), DevFiles (0.4)     # 使用するプラグイン
BuildTools: ocamlbuild                                      # ビルドツール

# ライブラリ名
Library example
  # ソースの場所
  Path:       src
  # ビルドするモジュール
  Modules: Example

もしくは、quickstartコマンドを使用します。
聞かれる質問に答えていくだけで、_oasisファイルを生成できます。

oasis quickstart

_oasisファイルが作成できたら、setupコマンドを実行します。

oasis setup

setupコマンドを実行すると、MakefileやAUTHORS.txt、README.mdなどのファイルが生成されているはずです。

Oasisプラグインで機能を追加できますが、試す分には下記の3つぐらいで十分です。

プラグイン 説明
META ライブラリ用のMETAファイルを生成する
StdFiles _oasisの定義から、README.txt, INSTALL.txt, AUTHORS.txtを生成する
DevFiles ビルドに必要な、Makefileを生成する(なくてもビルドはできます)

ライブラリの実装とテストコード

_oasisの設定で、ビルドするライブラリの指定をします。
ライブラリ名とソースの場所、そしてビルドするモジュールをカンマ区切りで列挙します。

# ライブラリ名
Library example
  # ソースの場所
  Path:       src
  # ビルドするモジュール
  Modules: Example

srcディレクトリを作成して、example.mlファイルを追加して、適当な関数を追加します。
ここでは、文字列を受け取って、文字列をそのまま返す関数を定義します。(解説の為の雑な関数です。)

let say msg = msg

次にこのモジュールのテストコードを書きます。
testsディレクトリを作成して、エントリポイントとなるtest.mlと、モジュール単体のテストコードexample_test.mlを作成します。

テストコードは、OUnitの独自演算子の>::、>:::を使用して記述します。(>:もあるが使うことはないと思う)

example_test.ml

Exampleモジュールのsay関数のテストは、受け取った引数をそのまま返すだけなのでこんな感じになります。

open OUnit2

(* say関数のテスト関数、テストの説明と関数でテストを表現する *)
let say_test =
  "echo string" >:: (fun _ -> assert_equal (Example.say "hello") "hello")

(* このテストモジュールのすべてのテストをまとめる *)
let tests =
  "all_tests" >::: [ say_test; ]

test.ml

エントリポイントではrun_test_tt_main関数でテストを実行するようにします。
ここで各テストジュールをまとめて、指定します。

open OUnit2

let all_tests = "all_tests" >::: [
  Example_test.tests
]

let () =
  run_test_tt_main all_tests;;

演算子の意味

>::

文字列とテスト関数より、新しいテストを返します。

let some_func1 = true

"test desc" >:: (fun ctx -> assert_bool "failed message" (some_func1 ()))

>:::

文字列とテストのリストより、新しいテストを返します。
複数のテストをグルーピングする為に使用します。

let some_func1 = true
let some_func2 = false

"all_tests" >::: [
    "test desc" >:: (fun ctx -> assert_bool "failed message" (some_func1 ()));
    "test desc" >:: (fun ctx -> assert_bool "failed message" (some_func2 ()))
]

テストコードのビルドとテストの実行

テストコードが追加できたら、_oasisファイルにテスト用の設定を追加します。

# テスト用のバイナリの名前
Executable test
 # テストコードの場所
  Path: tests
  # テストコードのエントリポイント
  MainIs: test.ml
  Build$: flag(tests)
  # ビルド結果のフォーマット(バイトコードにもできる)
  CompiledObject: native
  # 配布する必要がないので、false
  Install: false
  # 依存しているモジュールoUnitとテスト対象のライブラリを指定
  BuildDepends: oUnit, example

Test test
  Run$:               flag(tests)
  # テストコマンド
  Command:            $test
  # テストを実行するワーキングディレクトリ
  WorkingDirectory:   tests

テストを実行するには、configureでテスト用に設定を変えた後に、testでテストを実行できます。
これはテストコードを含んだ実行可能なバイナリを生成して、実行しています。

make configure CONFIGUREFLAGS=--enable-tests
make test

まとめ

ここで使用したファイルとかは、ここに適当に書いたOCamlのコードの中にあるので、実物を試したい人はご確認ください。 github.com