erlang でテストファースト

先日、erlang でテストを書くモジュールは無いのか?(id:cooldaemon:20070910:1189392523)と書いたところ、jijixi's diary - ユニットテストフレームワーク , 契約プログラミング , ほにほにネタ数点で eunit を教えて頂いたので、早速使ってみた。

インストール

cean で cean:intlall(eunit). もしくは

% svn co http://svn.process-one.net/contribs/trunk/eunit

とする。

使い方

まず、試しにテストを記述した foo.erl を準備する。

-module(foo).
-author('cooldaemon@gmail.com').

-include("eunit.hrl").

-export([bar/0, bar/1]).

bar_test_() ->
  [
    ?_assert(bar()  == buz),
    ?_assert(bar(1) == 2),
    ?_assert(bar(2) == 3)
  ].

bar() -> buz.
bar(N) -> N + 1.

eunit.hrl を include すると assert 用に定義されたマクロが使えるようになる。

次にコンパイルする。

% erlc -pa ./eunit/ebin -I./eunit/include foo.erl

この時、eunit の ebin と include を指定するのを忘れないようにする。

最後にテストを実行すると…

% erl -pa ./eunit/ebin -s foo test -s init stop
Erlang (BEAM) emulator version 5.5.3 [source] [async-threads:0] [hipe]

Eshell V5.5.3  (abort with ^G)
1>   All 3 tests successful.

bar_test_/0 を下記のように書き換えてテストを行うと…

bar_test_() ->
  [
    ?_assert(bar()  == buz),
    ?_assert(bar(1) == 4),
    ?_assert(bar(2) == 3)
  ].

下記のように出力される。

1> foo:11:bar_test_...*failed*
::error:{assertion_failed,[{module,foo},
                         {line,11},
                         {expression,"bar ( 1 ) == 4"},
                         {expected,true},
                         {value,false}]}
  in function foo:'-bar_test_/0-fun-2-'/0


=======================================================
  Failed: 1.  Aborted: 0.  Skipped: 0.  Succeeded: 2.

テスト対象モジュールにテストを含む事に関して

jijixi's diary - ユニットテストフレームワーク , 契約プログラミング , ほにほにネタ数点

eunit のちょっと変わった特徴としては、おそらくテスト対象のモジュールソースにテストをそのまま突っ込んでしまえることじゃないかと思う (上記の例ではやってないけど)。NOTEST マクロが定義されていると、コンパイル時にテスト関数 (*_test) は自動的に削られるので、beam ファイルに余計なものが含まれないようにできる (はず……確かめてないけど、eunit_striptests.erl とか見るとそないな感じ)。

社内のプログラムなら良いかなーと思います。
配布目的の場合は、利用者に eunit の使用を強要したくないので、ファイルを分けた方が良いと思います。
yamd の Makfile では、こんな感じにしました。

test.mk

EUNIT=/path/to/eunit

Makefile(一部抜粋)

include ../test.mk

#省略

subdirs:
        @for d in $(SUB_DIRECTORIES); do \
                (cd $$d; $(MAKE)); \
        done

#省略

test: subdirs
        @for d in $(SUB_DIRECTORIES); do \
                (cd $$d; $(MAKE) test); \
        done
        @echo Testing...
        @erl -noshell -pa ebin -pa $(EUNIT)/ebin -s yamd_test test -s init stop

src/Makefile(一部抜粋)

include ../test.mk

#省略

TEST_SOURCES=yamd_test.erl
TEST_OBJECTS=$(TEST_SOURCES:%.erl=$(EBIN)/%.$(EMULATOR))

#省略

$(TEST_OBJECTS): $(TEST_SOURCES)
        erlc -pa $(EBIN) -pa $(EUNIT)/ebin -W $(ERL_COMPILE_FLAGS) -I$(INCLUDE) -I$(EUNIT)/include -o$(EBIN) $<

test: $(TEST_OBJECTS)

make all ではテストを行わず、make test の時だけ yamd_test.erl をコンパイルしてテストを行うようにしています。

ちなみに eunit のライセンスは LGPL である為、私が作ったモノと一緒に配布したくないと考えてます。まー好みの問題ですね。
LGPL を勘違いしてた。LGPL はライセンスの伝染しないんですね。eunit を一緒に配布する事も検討しよう。