2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ErlangAdvent Calendar 2015

Day 20

common_test で spec を使う

Posted at

common_test で spec を使う

誰でも一度は作るというMySQLクライアントですが、いざ作ってみると困るのがテストです。本家のオプションを網羅する気はもちろんないですが、バージョン毎の分岐はやらないわけにはいきません。とりあえず 5.[5-7] で、それぞれ機能単位のテストを、一度に済ます、を目標としました、が、

さて、どうしたものかと。

ここは "多機能で大抵できる"、"覚えたら便利" という噂のcommon_testを使うトコ、ですよね?

まぁ、きっかけはともかく、状況としてはよくある話でしょうし、覚えているうちに書き留めておこうと思います。

※ ただし erlang-17.4 では動きません。orz

流れとしては、まず前提として

  • appで変数の初期値を設定し、
  • configでそれらを上書く

ようアプリを作ります。その上で

  • テスト毎にct_configを作り、
  • それをspec経由で読み込ませ

テストを実行します。テストの中ではcommon_test:init_per_suite/1

し、逆にcommon_test:end_per_suite/1

します。以下サンプルのechoアプリで見ていきます。

変数の初期値

./src/echo.app.src
{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 と渡ります。

変数の上書き

./src/echo_app.erl(一部)
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 です。

テストの記述

まんまです。

./test/echo_SUITE.erl(一部)
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).

テストそのものについては、、もう少しなんとかしたいんですが。

./test/echo_SUITE.erl(一部)
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といいつつ)大文字で戻す場合の設定ファイルです。

:./priv/ct/upper.ct_config
{config, [
          {echo, [
                  {type, upper}
                 ]}
         ]}.

これを *.spec 経由でコマンドに渡すことになります。

./priv/ct/default.spec
{label, "default"}.

{cover, "all.cover"}.

{config, "default.ct_config"}.

{suites, "../../test", all}.
./priv/ct/upper.spec
{label, "upper"}.

{cover, "all.cross_cover"}.

{config, "upper.ct_config"}.

{suites, "../../test", all}.

default と upper|lower に差(cover)がありますが、これはコードカバレッジの設定です。
echo では default に集約します。

テストの実行

コマンド:ct_runに *.spec を渡します。

./rebar3.config(一部)
{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 を見るとわかりますが、余計な処理が一つ入っています。

./Makefile(一部)
test: rm-logs ct cross_cover_analyse

cross_cover_analyse:
	$(ENV) escript priv/escript/$@.escript $(WORK)/test/logs
./priv/escript/cross_cover_analyse.escript
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 で見られます。

all_runs.png

コードカバレッジはこんな感じに。

cover_all_runs.png

まとめ

common_testは情報が少ないのがなにより辛いです。
でも今ならLearn you some Erlang (jp)があります!
それはきっとなんとかなります。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?