SMLUnitをSML#0.90からサルベージ

年明けて初めの記事ですね。今年もよろしくお願いします。


早速ですがSML#プロジェクトで開発されたユニットテストフレームワークであるSMLUnitをgithubにインポートしました(https://github.com/smlsharp/SMLUnit)。
昔はSML#のソースコードと一緒に配布されていたようですが、現状最新版の SML#1.2.0 にはSMLUnitのコードは含まれていない*1ため、手軽に使用することが出来なくなっていました。今回サルベージしたのでgit cloneしてすぐ使えます :)
SMLUnitはSML#プロジェクトの一環で開発されたユニットテスト用ライブラリですがSML'97の処理系なら問題無く動かせます。


sml-extに含まれる同名のライブラリを紹介しましたが、これとは違うライブラリです。まぁほとんど同じように使えます…。(というか似すぎてますねこれ。どっちかがフォークしたんでしょうね。)

SMLUnitの方があらかじめ定義されてるアサート用比較関数がかなり多いので便利そうです。*2

インストール

SML#用のmakefileを書いたのでmakeすればよいです:) 残念ながらreplで使うにはファイルを地道にuseする必要があります。*3
あとは必要に応じて、SML#ライブラリを置いておく場所にsmlunitlib.smiとsrc/main/*.smiをコピー。
SML/NJとMLtonに関してはreadme.mdを見てください。手順はsml-extの時と全く同じです。

使用例

sml-ext/UnitTestsの紹介に使用した例をSMLUnit用に書き直してみました。

(* test.smi *)
_require "basis.smi"
_require "smlunitlib.smi"
(* test.sml *)
structure Test =
struct
  open SMLUnit
  open Assert
  val $ = Test.TestLabel
  val & = Test.TestList
  val % = Test.TestCase
  val ? = assertTrue
  val assertEqualEq = assertEqualInt

  fun test () =
    $ ("Test" (* 全体に名前付ける *)
    , &[ % (fn()=> assertEqualEq  0 0) (* 0==0 *)
       , % (fn()=> assertEqualEq  0 1) (* 0==1 *)
       , % (fn()=> assertEqualEq  0 2) 
       , % (fn()=> assertEqualInt 1 0)
	   (* ケースに名前を付ける *)
       , $ ("1==1", % (fn()=> assertEqualInt 1 1))
       , % (fn()=> assertEqualInt 1 2)
       , % (fn()=> assertEqualReal 2.0 0.0)
	   (* 特殊化された比較アサート *)
       , % (fn()=> assertEqualReal 2.1 2.1)
       , % (fn()=> assertEqualReal 2.2 2.19999)
       , % (fn()=> assertEqualReal 3.14 3.14159)
       , % (fn()=> ? (6 = foldr op+ 0 [1,2,3]))
       , $ ("sum[1..10]=55",     (* ちょっと実際的?なケース *)
            % (fn()=> ? (55 = foldr op+ 0
                            (List.tabulate(10,fn i=> i)))))
      ])
end
structure T = SMLUnit.TextUITestRunner;
val _ = T.runTest {output=TextIO.stdOut} (Test.test()); (* 即テスト実行 *)

assertEqual系関数がアンカリー化されてるくらいでsml-ext.UnitTestsとほとんど同じですね(記号の関数は自分で定義してますけど)。
sml-ext用smiを書く(+管理する)よりUnitTestsから移行する方が楽そうです。


出力結果も載せておきます。

$ smlsharp test.sml -o test && ./test
.FFF.FF.FF.F
tests = 12, failures = 8, errors = 0
Failures:
//Test/2: expected:<0>, actual:<1>
//Test/3: expected:<0>, actual:<2>
//Test/4: expected:<1>, actual:<0>
//Test/6: expected:<1>, actual:<2>
//Test/7: expected:<2.0>, actual:<0.0>
//Test/9: expected:<2.2>, actual:<2.19999>
//Test/10: expected:<3.14>, actual:<3.14159>
//Test/12/sum[1..10]=55: expected:<true>, actual:<false>
Errors:

はい問題無いですね。
というわけでSML#書くときもユニットテスト書きましょう。

*1:1系列から周辺ツールのコードが削除されている

*2:ただし ''a * ''a -> unit みたいな関数は無いのでお手軽さは多少減るかも知れない。

*3:何かいい手があったら教えてください