type holyshared = Engineer<mixed>

技術的なことなど色々

ppx_inline_testでモジュール内にテストコードを書く

これはML Advent Calendar 2017の記事です。
枠が空いていて、誰も書いてなかったので書きました。

OCamlのテスト用のツールで、構文拡張を使用したppx_inline_testがあります。
今まで、OUnitを使っていたのですが試しに使用してみました。

使用するのに必要なライブラリは下記の通りです。

  • ppx_jane - Jane Streetが公開している構文拡張ライブラリ群
    • ほんとは必要ないパッケージがあるので、使いたくありませんでした(ビルドが面倒だったので使いました。)
  • jbuilder - Jane Streetが公開しているOCamlのビルドツール

サンプルのプロジェクトは下記のリポジトリに用意してあります。
ppx_inline_test_with_jbuilder

OPAM用のファイルを用意する

ルートディレクトリにライブラリ用のOPAMファイルを用意し、 build、build-testにビルド用のコマンドを追加します。

そのほかの項目はメタ情報を参照して、必要な物を追加してください。

opam-version: "1.2"
name: "example"
version: "0.1.0"
authors: ["YOUR NAME"]

build: [
  ["jbuilder" "build" "-p" name "-j" jobs]
]

build-test: [
  ["jbuilder" "runtest" "-p" name]
]

depends: [
  "jbuilder" {build}
]
available: [ ocaml-version >= "4.06.0" ]

テストコードを含んだライブラリの用意

ライブラリの用意

src/exampleディレクトリを作成して、example1.mlというファイル名でライブラリのコードをおきます。

ここでは、say関数を定義して、その関数のテストコードを用意してます。
このテストはわざと落ちるようにしています。

またtest_unitはテストがパスした場合は()、失敗した場合は例外(exn)を投げる必要があります。

let say () = "hello"

let%test_module _ = (module struct
  let%test_unit "say hello" =
    assert (say () = "world")
end)

ライブラリのビルド設定

ビルド用のファイルjbuildを用意します。

library_flags-linkallを指定します、これは指定しないと、テストモジュールがリンクされないです。

また、preprocessppx_jane, ppx_driver.runnerを指定して、テストコードの構文拡張を処理できるようにします。

(jbuild_version 1)

(library (
  (public_name example.example1)
  (name example_example1)
  (library_flags -linkall)
  (preprocess (pps (ppx_jane ppx_driver.runner)))
))

テストランナーの用意

テストランナーのコード追加

testディレクトを作成して、テストランナーのコードを用意します。
ファイル名はtest_runner.mlにします。

テストランナーのソースには下記のコードを追加します。

Ppx_inline_test_lib.Runtime.exit ()

テストのランナーのビルド設定

まずテスト対象のライブラリをまとめたライブラリをビルドできるようにします。
そして、executableでテストランナーの依存ライブラリに加えます。

その後に、aliasruntestを追加します。
test_runner.exeのサブコマンドでinline-test-runnerを指定し、次にテスト対象のライブラリ名を指定します。

(library (
  (name example_all)
  (libraries (example.example1))
  (library_flags -linkall)
  (preprocess (pps (ppx_jane ppx_driver.runner)))
))

(executable (
  (name test_runner)
  (libraries (ppx_inline_test.runner.lib example_all))
))

(alias
  ((name runtest)
  (deps (test_runner.exe))
  (action (run ${<} inline-test-runner example_example1))))

(jbuild_version 1)

実際にテスト実行する際に、実行されるコマンドは下記の通りです。
ライブラリ名は、パブリックな名前だと実行できませんでした。

_build/default/test/test_runner.exe inline-test-runner example_example1

テストの実行

各手順が終われば、下記のコマンドを実行するとテストが実行でき、テストが落ちるはずです。

jbuilder runtest

落ちたのが確認できた場合、今度はライブラリのコードを修正して、テストが通ることを確認してみてください。

感想

テストが実行できるようになるまで、結構時間がかかりました。
特に-linkallフラグを指定していなくて、テストが実行できなかったのにハマりました。

また、ppx_assertを使いたかったのですが、テストモジュールに別途必要なモジュールをopenしないとコンパイルが通らなくて、若干面倒でした。

モジュールにテストコードを直接かけるのはいいですが、OUnitの方がわかりやすいかなーといいう印象です。