common_test で spec を使う
誰でも一度は作るというMySQLクライアントですが、いざ作ってみると困るのがテストです。本家のオプションを網羅する気はもちろんないですが、バージョン毎の分岐はやらないわけにはいきません。とりあえず 5.[5-7] で、それぞれ機能単位のテストを、一度に済ます、を目標としました、が、
さて、どうしたものかと。
ここは "多機能で大抵できる"、"覚えたら便利" という噂のcommon_testを使うトコ、ですよね?
まぁ、きっかけはともかく、状況としてはよくある話でしょうし、覚えているうちに書き留めておこうと思います。
※ ただし erlang-17.4 では動きません。orz
流れとしては、まず前提として
ようアプリを作ります。その上で
テストを実行します。テストの中ではcommon_test:init_per_suite/1で
- application:load/1で初期値を読み直し、
- application:set_env/3で変数を上書き、
- application:start/1でアプリを起動
し、逆にcommon_test:end_per_suite/1で
- application:stop/1でアプリを停止、
- application:unload/1でリセット
します。以下サンプルのechoアプリで見ていきます。
変数の初期値
{application, echo,
[
{description, "echo"},
{vsn, "0.1"},
{registered, [echo_sup]},
{included_applications, []},
{applications, [kernel,stdlib]},
{mod, {echo_app, [
]}}
]}.
{env, ...} ではなく {mod, ...} だけ使うのが好みです。今回は [ ](未設定)です。
この後 echo_app, echo_sup, echo_server と渡ります。
変数の上書き
args(List) ->
[
{type, config(type,List,undefined)}
].
config(Key, List, Default) ->
case application:get_env(echo,Key) of
undefined ->
proplists:get_value(Key, List, Default);
{ok, Val} ->
Val
end.
ベタですね。
supervisorの設定はsupervisor:init/1に書かない派なのでModule:start/2でガリガリやります。
ただでさえ複雑になるgen_serverには環境を持ち込みません。
typeの期待値は upper|lower、未設定なら undefined です。
テストの記述
まんまです。
suite() -> [
{require, config}
].
init_per_suite(Config) ->
_ = application:load(echo),
ok = set_env(ct:get_config(config)),
start_test(Config).
end_per_suite(Config) ->
ok = stop_test(Config),
application:unload(echo).
set_env([]) ->
ok;
set_env([{A,L}|T]) ->
[ ok = application:set_env(A,P,V) || {P,V} <- L ],
set_env(T).
テストそのものについては、、もう少しなんとかしたいんですが。
call_test(upper, _Config) ->
L = [
{ ["Hello"], {ok,"HELLO"} }
],
[ E = test(call,A) || {A,E} <- L ];
call_test(lower, _Config) ->
L = [
{ ["Hello"], {ok,"hello"} }
],
[ E = test(call,A) || {A,E} <- L ];
call_test(undefined, _Config) ->
L = [
{ ["Hello"], {ok,"Hello"} }
],
[ E = test(call,A) || {A,E} <- L ].
テストの設定ファイル
どこに置くか決まりはないようなので、./priv/ct に置いています。
$ ls ./priv/ct
all.cover default.ct_config lower.ct_config upper.ct_config
all.cross_cover default.spec lower.spec upper.spec
*.ct_config は common_test の設定ファイルです。
下記は(echoといいつつ)大文字で戻す場合の設定ファイルです。
{config, [
{echo, [
{type, upper}
]}
]}.
これを *.spec 経由でコマンドに渡すことになります。
{label, "default"}.
{cover, "all.cover"}.
{config, "default.ct_config"}.
{suites, "../../test", all}.
{label, "upper"}.
{cover, "all.cross_cover"}.
{config, "upper.ct_config"}.
{suites, "../../test", all}.
default と upper|lower に差(cover)がありますが、これはコードカバレッジの設定です。
echo では default に集約します。
テストの実行
コマンド:ct_runに *.spec を渡します。
{ct_opts, [
{spec, [
"priv/ct/upper.spec",
"priv/ct/lower.spec",
"priv/ct/default.spec"
]}
]}
実行はrebar3におまかせです。
ただし -erlargs は使えないので、必要ならct_runを直接たたく必要があります。
$ make test
===> Verifying dependencies...
===> Compiling echo
===> Running Common Test suites...
%%% echo_SUITE ==> call_test: OK
Cover analysing... done
%%% echo_SUITE ==> call_test: OK
Cover analysing... done
%%% echo_SUITE ==> call_test: OK
Cover analysing... done
All 3 tests passed.
Cover analysing...
(大量のログ)
done
$
Makefile を見るとわかりますが、余計な処理が一つ入っています。
test: rm-logs ct cross_cover_analyse
cross_cover_analyse:
$(ENV) escript priv/escript/$@.escript $(WORK)/test/logs
tests(Dir) ->
[ {all,filename:join([Dir,E])} || E <- lists:reverse(filelib:wildcard("ct_run.*",Dir)) ].
main([Dir]) ->
ct_cover:cross_cover_analyse(details, tests(Dir)).
ct_cover:cross_cover_analyse/2は誰かがやってくれていたはずなんですが、わからないので自力です。
しかも厳密には正しくないコードですが、気がつかなかった事にしてやって下さい。
テストの結果
./.rebar3/test/logs/all_runs.html で見られます。
コードカバレッジはこんな感じに。
まとめ
common_testは情報が少ないのがなにより辛いです。
でも今ならLearn you some Erlang (jp)があります!
それはきっとなんとかなります。