omake + oUnitでTDD!

明日は、TDD Bootcamp名古屋です。
というわけで、自分のoUnit + omakeでTDDな環境を晒してみます。もっとクールな方法があったら教えてください><。

ちなみに、構築したサンプルはGitHub - mzp/ounit-example-1: OUnit exampleに置いてあります。

ディレクトリ構造

ディレクトリ構造はこんな感じで、本番コード(./src)とテストコード(./test)が別ディレクトリに分かれています。

.
|-- OMakefile
|-- OMakeroot
|-- src
|   |-- OMakefile
|   |-- fact.ml
|   `-- main.ml
`-- test
    |-- OMakefile
    `-- fact_test.ml

ルートのOMakefile

ルートのOMakefileではサブディレクトリを認識させます。ついでにocamlfindも有効にしておきます。

.PHONY: all clean check

USE_OCAMLFIND = true
OCAML_FLAGS=-w A -warn-error A

if $(not $(OCAMLFIND_EXISTS))
   eprintln(This project requires ocamlfind, but is was not found.)
   eprintln(You need to install ocamlfind and run "omake --configure".)
   exit 1

NATIVE_ENABLED = $(OCAMLOPT_EXISTS)
BYTE_ENABLED = $(not $(OCAMLOPT_EXISTS))

.SUBDIRS: src test

clean:
	rm -f *~ *.opt *.cmi *.cmx *.o *.omc

本番コード側

適当なモジュールを書きます。とりあえず階乗で。

(* src/fact.ml *)
let rec fact n =
  if n = 0 then
    2 (* BUGGGGYYYY *)
  else
    n * (fact (n - 1))

そしてOMakefile。
普通のOCamlプログラムとしてビルドするだけでなく、ライブラリとしてもビルドします。

# src/OMakefile
.PHONY: all clean

FILES[] =
	fact
	main

LIB = target
PROGRAM = main

.DEFAULT: $(OCamlProgram $(PROGRAM), $(FILES))

OCamlLibrary($(LIB), $(FILES))

clean:
	rm -f *~ *.opt *.cmi *.cmx *.o *.omc *.cma *.cmxa $(PROGRAM) *.a

テストコード側

oUnitを使ったテストコードを書きます。

(* test/fact_test.ml *)
open OUnit
open Fact

let _ = run_test_tt_main begin "fact.ml" >::: [
  "fact(3)" >:: begin fun () ->
    assert_equal 6 (fact 3)
  end
] end

そしてテストプログラムをビルドします。
このとき先ほど作ったライブラリを使うようにします。

# test/OMakefile
.PHONY: all clean check
OCAMLINCLUDES += ../src

FILES[] =
	fact_test

OCAMLPACKS[] =
	oUnit

PROGRAM = test
OCAML_LIBS += ../src/target

clean:
	rm -f *~ *.opt *.cmi *.cmx *.o *.omc *.cma *.cmxa

.DEFAULT: all

all : $(OCamlProgram $(PROGRAM), $(FILES))

check : all
	./$(PROGRAM)

実行例

$ omake check
*** omake: reading OMakefiles
*** omake: finished reading OMakefiles (0.23 sec)
- build test <check>
+ ./test
F
======================================================================
Failure: 1:0:fact

OUnit: not equal
----------------------------------------------------------------------
Ran: 1 tests in: 0.00 seconds.
FAILED: Cases: 1 Tried: 1 Errors: 0 Failures: 1 Skip:0 Todo:0
*** omake: 38/40 targets are up to date
*** omake: failed (4.16 sec, 0/3 scans, 7/14 rules, 13/130 digests)
*** omake: targets were not rebuilt because of errors:
   <phony <test/check>>