common_test で spec を使う

# common_test で spec を使う 

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

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

ここは "多機能で大抵できる"、"覚えたら便利" という噂の[common_test](http://www.erlang.org/doc/apps/common_test/users_guide.html)を使うトコ、ですよね？

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

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

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

- [app](http://www.erlang.org/doc/man/app.html)で変数の初期値を設定し、
- [config](http://www.erlang.org/doc/man/config.html)でそれらを上書く

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

- テスト毎に[ct_config](http://www.erlang.org/doc/apps/common_test/config_file_chapter.html)を作り、
- それを[spec](http://www.erlang.org/doc/apps/common_test/run_test_chapter.html#id77084)経由で読み込ませ

テストを実行します。[テストの中](http://www.erlang.org/doc/man/common_test.html)では[common_test:init_per_suite/1](http://www.erlang.org/doc/man/common_test.html#Module:init_per_suite-1)で

- [application:load/1](http://www.erlang.org/doc/apps/kernel/application.html#load-1)で初期値を読み直し、
- [application:set_env/3](http://www.erlang.org/doc/apps/kernel/application.html#set_env-3)で変数を上書き、
- [application:start/1](http://www.erlang.org/doc/apps/kernel/application.html#start-1)でアプリを起動

し、逆に[common_test:end_per_suite/1](http://www.erlang.org/doc/man/common_test.html#Module:end_per_suite-1)で

- [application:stop/1](http://www.erlang.org/doc/apps/kernel/application.html#stop-1)でアプリを停止、
- [application:unload/1](http://www.erlang.org/doc/apps/kernel/application.html#unload-1)でリセット

します。以下サンプルの[echo](https://github.com/tomaon/echo)アプリで見ていきます。

## 変数の初期値

```erlang:./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 と渡ります。

## 変数の上書き
```erlang:./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](http://www.erlang.org/doc/man/supervisor.html)の設定は[supervisor:init/1](http://www.erlang.org/doc/man/supervisor.html#Module:init-1)に書かない派なので[Module:start/2](http://www.erlang.org/doc/apps/kernel/application.html#Module:start-2)でガリガリやります。
ただでさえ複雑になる[gen_server](http://www.erlang.org/doc/man/gen_server.html)には環境を持ち込みません。

typeの期待値は upper|lower、未設定なら undefined です。

## テストの記述

まんまです。

```erlang:./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).
```

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

```erlang:./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 に置いています。

```bash
$ 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](http://www.erlang.org/doc/apps/common_test/config_file_chapter.html) は common_test の設定ファイルです。
下記は（echoといいつつ）大文字で戻す場合の設定ファイルです。

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

これを *.[spec](http://www.erlang.org/doc/apps/common_test/run_test_chapter.html#id77084) 経由でコマンドに渡すことになります。

```erlang:./priv/ct/default.spec
{label, "default"}.

{cover, "all.cover"}.

{config, "default.ct_config"}.

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

```erlang:./priv/ct/upper.spec
{label, "upper"}.

{cover, "all.cross_cover"}.

{config, "upper.ct_config"}.

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

default と upper|lower に差（cover）がありますが、これは[コードカバレッジ](http://www.erlang.org/doc/man/cover.html)の設定です。
echo では default に集約します。

## テストの実行

コマンド:[ct_run](http://www.erlang.org/doc/man/ct_run.html)に *.spec を渡します。

```erlang:./rebar3.config(一部)
{ct_opts, [
           {spec, [
                   "priv/ct/upper.spec",
                   "priv/ct/lower.spec",
                   "priv/ct/default.spec"
                  ]}
          ]}
```

実行は[rebar3](https://www.rebar3.org/)におまかせです。
ただし -erlargs は使えないので、必要なら[ct_run](http://www.erlang.org/doc/man/ct_run.html)を直接たたく必要があります。

```bash
$ 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 を見るとわかりますが、余計な処理が一つ入っています。

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

cross_cover_analyse:
	$(ENV) escript priv/escript/$@.escript $(WORK)/test/logs
```

```erlang:./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](http://www.erlang.org/doc/man/ct_cover.html#cross_cover_analyse-2)は誰かがやってくれていたはずなんですが、わからないので自力です。
しかも厳密には正しくないコードですが、気がつかなかった事にしてやって下さい。

## テストの結果
./.rebar3/test/logs/all_runs.html で見られます。

![all_runs.png](https://qiita-image-store.s3.amazonaws.com/0/28253/0c688473-40fc-268b-3d61-b1baf83a8ab1.png "all_runs.png")

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

![cover_all_runs.png](https://qiita-image-store.s3.amazonaws.com/0/28253/7f220c82-5323-1c82-641b-c42b3bf974aa.png "cover_all_runs.png")

## まとめ
[common_test](http://www.erlang.org/doc/apps/common_test/users_guide.html)は情報が少ないのがなにより辛いです。
でも今なら[Learn you some Erlang](http://learnyousomeerlang.com/common-test-for-uncommon-tests) [(jp)](http://www.ymotongpoo.com/works/lyse-ja/ja/31_common_test_for_uncommon_tests.html)があります！
それはきっとなんとかなります。