前回とはうって変わって、ちょっとだけassert文をどうにか出来ないかなぁという感じの方向性で作ってみました。
module type ASSERT = sig type t val cmp : (t -> t -> bool) val printer : (t -> string) end module MakeAssert(AssertType:ASSERT) : sig type t = AssertType.t val assert_equal : string -> t -> t -> unit val (@=) : string -> t * t -> unit end = struct type t = AssertType.t let assert_equal msg expected actual = OUnit.assert_equal ~cmp:AssertType.cmp ~printer:AssertType.printer ~msg:msg expected actual let (@=) msg (expected, actual) = assert_equal msg expected actual end module AssertInt = MakeAssert(struct type t = int let cmp = (=) let printer = string_of_int end) module AssertString = MakeAssert(struct type t = string let cmp = (=) let printer s = s end) module AssertFloat = MakeAssert(struct type t = float let cmp = (=) let printer = string_of_float end) let (@!) msg (e, f) = OUnit.assert_raises ~msg:msg e f
MakeAssertというファンクタを用いて、様々な型に対応するassert_* を作ってしまおうと言う感じで作ってみました。
正直、作ってみたところ、OUnitのassert_* で型に依存するような実装になっているのがassert_equalしかなかったので微妙でした。
あとはassert_boolに対応する (@?) のような書き方をassert_equalやassert_raisesで出来ないか試してみています。
ファンクタ内にパラメタライズドテストが簡単にできるようなヘルパ関数を用意してあげるとさらに便利になるかなと思いますが、それでも現状のOUnitに比べてそれほど利便性が向上したり、可読性が良くなったりするような感じではないですね。。。
個人的には久々にファンクタを使ったり、First class moduleを試してみたりしていたので満足です^^
追記:
そういえば、First class moduleとか、type variableにexplicit に名前を付けられるようになったとかが3.12にあるので、それつかってみようかなと。
let ex_assert_equal (type s) (cmp : s -> s -> bool) (printer : s -> string) = let module Ass = MakeAssert(struct type t = s let cmp = cmp let printer = printer end) in Ass.assert_equal
書いてみたけど、あんまりありがたみはない気がする。ただこっちの方がassert_equalしか恩恵がないところを考えると、モジュール一個作るよりはいい感じかな。正直assert_equalでしかここでのコードの恩恵がない事を考慮すると、関数で対処するのが最善だし使うならこっちかな。