OCamlのテスティングフレームワークを作ってみたり

練習もかねて、テスティングフレームワークを書いてみてました。

最初はこんなに大きなものを作る気がなかったので、バージョン管理してなかった(汗

正直言ってあまり出来は良くない。複雑だし、いろいろメンドイ。それにOUnitよりいい点があまり無い。


方向性は
演算子によるアサーション
・比較の型の選択をモジュール(ファンクター)で行う


コードは
https://gist.github.com/3246539


汚いな、さすが管理されていないコードきたない。


とりあえず次のように書ける。
T_* が対応するテスト用の関数を持っている。あとはsubtypeを持つものはファンクターでsubtypeの T_* を渡す。

module T_int_list = T_list(T_int)
module T_string_list = T_list(T_string)
let list_test =
  test_suite "List" [
    test_case "length" T_int.([
      "length [] is 0" @: (fun () ->
        List.length [] @?= 0 );

      "length [1] is 1" @: (fun () ->
        List.length [1] @?= 1 );

      "length [1; 2; 3] is 3" @: (fun () ->
        List.length [1; 2; 3] @?= 3 )
    ]);

    test_case "hd" [
      T_int.(
      "hd [1] is 1" @: (fun () ->
        List.hd [1] @?= 1 ));

      T_string.(
      "hd [\"a\"; \"b\"] is \"a\"" @: (fun () ->
        List.hd ["a"; "b"] @?= "a" ));

      "hd [] raises Failure" @: (fun () ->
        (fun () -> List.hd [] ) @!= (Failure "hd"))
    ];

    test_case "tl" [
      T_int_list.(
      "tl [1] is []" @: (fun () ->
        List.tl [1] @?= [] ));
      
      T_string_list.(
      "tl [\"a\"; \"b\"] is [\"b\"]" @: (fun () ->
        List.tl ["a"; "b"] @?= ["b"] ));

      "tl [] raises Failure" @: (fun () ->
        (fun () -> List.tl []) @!= (Failure "tl"))
    ]
  ]

print_endline (run_test_suite [list_test]);;

(*
TestSuite List
==============================
TestCase length : Pass
TestCase hd : Pass
TestCase tl : Pass
*)

あとパラメタライズドテストを実装して、次のように書けるようにしました。

let fizzbuzz x =
  let div_3, div_5 = x mod 3 = 0, x mod 5 = 0 in
  match div_3, div_5 with
  | true, true -> "fizzbuzz"
  | true, false -> "fizz"
  | false, true -> "buzz"
  | false, false -> string_of_int x

let fizzbuzz_test = let open T_string in
  test_suite "FizzBuzz" [
    test_case_param "Divided 3" ~test_f:test
      (fun x -> fizzbuzz x @?= "fizz")
      [ 3; 6; 9; 12; 18; 30 ];

    test_case_param "Divided 5" ~test_f:test
      (fun x -> fizzbuzz x @?= "buzz")
      [ 5; 10; 20; 25; 35; 40 ];

    test_case_param "Divided 3 and 5" ~test_f:test
      (fun x -> fizzbuzz x @?= "fizzbuzz")
      [ 15; 30; 45; 60; 75; 90 ];

    test_case_param "Not divided 3 and 5" ~test_f:test
      (fun (x, y) -> fizzbuzz x @?= y)
      [ (1, "1"); (2, "2"); (4, "4"); (7, "7"); (14, "14"); (16, "16"); ]
  ]

print_endline (run_test_suite [fizzbuzz_test])

(*
TestSuite FizzBuzz
==============================
TestCase Divided 3 : Failure
    actual:fizzbuzz is not equal to expected:fizz

TestCase Divided 5 : Pass
TestCase Divided 3 and 5 : Pass
TestCase Not divided 3 and 5 : Pass
*)

この辺り、少し工夫したらOUnitに少し関数を追加したら出来そうな感じのものも多いので、OUnitを自分用にカスタマイズするというのも結構いいかもしれないです。

最初はもう少し気軽に書けるように出来るかと思ったけど、そううまくは出来てないし、設計もひどいし、とりあえずこいつはいまのところあまり使いたくない。。。だってフレームワークの実装も、使ったときのコードもイケてないもん。

pa_ounitのように、P4を上手に使いつつのライブラリだったら良かったかな。テスト失敗時の表示のネックをその辺りで解消できればいいかな。P4頑張るしか無いね。

広告を非表示にする