Common Test

まだ、解らない事だらけだが、使い始める所まで辿り着いたのでメモを残す。

run_test コマンドの準備

run_test とは、名前の通り、テストを実行してくれるコマンドで、実際は、erl コマンドを実行するシェルスクリプト
作業は、Common Test がインストールされているディレクトリに移動して install.sh を実行するだけ。

% cd /path/to/common_test-X.X.X
% sudo ./install.sh local

上記を実行すると、/path/to/common_test-X.X.X/priv/bin 配下に run_test が作成される。

手元の環境では、下記のパスに Common Test がインストールされていた。

OS erlang R12B をインストールする際に使ったパッケージ Common Test のインストール先
FreeBSD 6.2 Ports /usr/local/lib/erlang/lib/common_test-1.3.0
OSX 10.4.11 MacPort /opt/local/lib/erlang/lib/common_test-1.3.0

ちなみに・・・、OSX では、/path/to/common_test-X.X.X/priv/bin ディレクトリを、install.sh 実行前に予め作成しておかないとエラーになった。

run_test のオプション

-dir

テスト対象のディレクトリを指定する為に使う。
指定されたディレクトリ名が '_test' で終わらない場合、テスト対象とは見なされず、指定されたディレクトリ配下の test ディレクトリが対象となる。
この場合、test ディレクトリが存在しないとエラーとなる。

-logdir

テストログの出力先のディレクトリを指定する為に使う。
ログは、HTML で出力する。

-cover

下記、Code Coverage Analysis を参照の事。

-stylesheet

下記、ログの出力を参照の事。

テストの書き方(概要)

Common Test では、テストスイートはモジュール単位で、テストケースは関数単位で記述する。
モジュール名は xxxx_SUITE という形式にする事。

下記に、テストスイートの非常にシンプルな例を示す。

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

-compile(export_all).

all() -> [bar].

bar() -> [].
bar(_Config) -> ok.

テストスイート内に記述できるコールバック関数

all/0

関数名(テストケース)のリストを返すようにする。
Common Test は、リストの並び順に従ってテストケースを評価する。

all() -> [foo, bar].

{skip, Reason} を返すと、モジュール内の全てのテストケースは評価されずにスキップされる。
Reason はログに記録される。

all() -> {skip, "Skip this suite."}.
suite/0

一番始めに一度だけ評価される。
テストスイートの設定のリストを返すようにする。

設定の名前(atom) 概要
timetrap テストスイート評価の制限時間を指定する。詳細は下記、timetrap を参照の事
require 未調査
userdata 使い道が不明。詳細は下記、userdata を参照の事
silent_connections 未調査
stylesheet 下記、ログの出力を参照の事
suite() ->
  [
    {timetrap, {minutes, 1}},
    {userdata, [{key1, "value1"}, {key2, "value2"}]}
  ].
init_per_suite/1

suite/0 の次に一度だけ評価される。
主にテストスートの準備で使う。

init_per_suite(Config) ->
  application:start(ssl),
  application:start(inets),
  Config.

init_per_suite/1 を記述した場合、end_per_suite/1 も記述しておかないとエラーとなる。

デフォルトの Config を受け取り、設定したい Config を返すようにする。
Config は、[{Key, Value}] 形式。
ct.hrl をインクルードしておくと、設定した値は ?config で取り出せる。

% ..snip..
-include("ct.hrl").
% ..snip..
init_per_suite(Config) ->
  [{suite_key, "suite_value"} | Config].
% ..snip..
check_config(Config) ->
  Value = ?config(suite_key, Config), % Value = "suite_value"
  ok.
% ..snip..

{skip, Reason} を返すとテストスイートはスキップされる。(all/0 の {skip, Reason} と同様)
{skip_and_save, Reason, Config} で、スキップしつつ Config を次のテストスイートへ引き渡せる。

下記のように設定すると・・・

-module(test1_SUITE).
% ..snip..
init_per_suite(Config) ->
  {skip_and_save, "Skip and save.", [{save_key, "save_value"}]}.
% ..snip..

下記のように取り出せる

-module(test2_SUITE).
% ..snip..
-include("ct.hrl").
% ..snip..
init_per_suite(Config) ->
  {test1_SUITE, OldConfig} = ?config(saved_config, Config),
  SaveValue = ?config(save_key, OldConfig), % SaveValue = "save_value"
  Config.
% ..snip..

単純に、Config に {saved_config, {config1_SUITE, [{save_key,"save_value"}]}} というタプルが入っているだけ。
saved_config が入ったままの Config を戻しているが、テストケースに saved_config が渡る事は無い。

end_per_suite/1

一番最後に一度だけ評価される。
主にテストスイートの後始末で使う。

end_per_suite(_Config) ->
  application:stop(inets),
  application:stop(ssl),
  ok.

{save_config, Config} を返すと Config を次のテストスイートに引き渡す。(init_per_suite/1 の {skip_and_save, Reason, Config} と同様)

init_per_testcase/2

テストケース毎に、テストケースが呼ばれる前に評価される。
主にテストケースの準備で使う。
第一引数は、テストケースの関数名を受け取る。

init_per_testcase(testcase1, Config) ->
  application:start(mnesia),
  Config;

init_per_testcase(testcase2, Config) ->
  application:start(inets),
  Config;

init_per_testcase(_TestCase, Config) ->
  Config.

init_per_testcase/2 を記述した場合、end_per_testcase/2 も記述しておかないとエラーとなる。

init_per_suite/1 同様に Config の設定ができ、?config で取り出す事ができる。

% ..snip..
-include("ct.hrl").
% ..snip..
init_per_testcase(config_check, Config) ->
  [{testcase_key, "testcase_value"} | Config];

init_per_testcase(_TestCase, Config) ->
  Config.
% ..snip..
config_check(Config) ->
  Value = ?config(testcase_key, Config), % Value = "testcase_value"
  ok.
% ..snip..

{skip, Reason} を返すとテストケースはスキップされる。

end_per_testcase/2

テストケース毎に、テストケースが呼ばれた後に評価される。
主にテストケースの後始末で使う。

end_per_testcase(testcase1, _Config) ->
  application:stop(mnesia),
  ok;

end_per_testcase(testcase2, _Config) ->
  application:stop(inets),
  ok;

init_per_testcase(_TestCase, _Config) ->
  ok.

{save_config, Config} を返すと Config を次のテストケースに引き渡す。

sequences/0

シーケンス(テスケースの集合)のリストを返すようにする。
シーケンスを評価中、一つでもテストケースが失敗すると、それ以降のシーケンス内のテストケースは評価されずにスキップされる。
ただし、能動的にテストケースをスキップした場合、失敗ではない為、シーケンス内のテスケースの評価は続く。

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

-compile(export_all).

-include("ct.hrl").

sequences() ->
  [
    {sequencesA, [testA1, testA2, testA3]},
    {sequencesB, [testB1, testB2, testB3]}
  ].

all() ->
  [
    test1,
    {sequence, sequencesA},
    test2,
    {sequence, sequencesB}
  ].

init_per_testcase(testA2, _Config) -> {skip, "skip testA2"};
init_per_testcase(_TestCase, Config) -> Config.

end_per_testcase(_TestCase, _Config) -> ok.

test1() -> [].
test1(_Config) -> ok.

test2() -> [].
test2(_Config) -> 1 = 2.

testA1() -> [].
testA1(_Config) -> ok.

testA2() -> [].
testA2(_Config) -> ok.

testA3() -> [].
testA3(_Config) -> ok.

testB1() -> [].
testB1(_Config) -> ok.

testB2() -> [].
testB2(_Config) -> 1 = 2.

testB3() -> [].
testB3(_Config) -> ok.

上記は、test1、testA1、testA3、test2(失敗)、testB1, testB2(失敗) の順に評価され、testA2 と testB3 はスキップされる。

testcase/0

テストケースの設定をリストで返すようにする。

設定の名前(atom) 概要
timetrap テストケース評価の制限時間を指定する。詳細は下記、timetrap を参照の事
require 未調査
userdata 使い道が不明。詳細は下記、userdata を参照の事
silent_connections 未調査

timetrap で指定する秒数には、init_per_testcase/2 と end_per_testcase/1 の時間が含まれる。

testcase/1

テストケース本体。
引数として Config を受け取る。
失敗の場合は、crashe させるか ct:fail/[0,1] を呼ぶ。

戻り値として、下記の内、どれかを返すようにする。

  • {skip, Reason}
  • {comment, Comment}
  • {save_config, Config}
  • {skip_and_save, Reason, Config}

上記以外の戻り値は、全て成功とみなされる。

ログの出力

ct:comment/1

コメントをテスト結果(HTML 形式)に残す。
テストケース毎にコメントは一つのみである為、テストケース内で複数回 ct:comment/1 を評価すると、最後の一つだけが有効。
テストケースの戻り値として {comment, Comment} を返す事と、 ct:comment/1 を評価する事は同義。

testcase1() -> [].
testcase1(_Config) ->
  ct:comment("comment1"),
  ok.

testcase2() -> [].
testcase2(_Config) ->
  ct:comment("comment2"),
  ct:comment("comment3"), % ←これが有効
  ok.

testcase3() -> [].
testcase3(_Config) ->
  ct:comment("comment4"),
  {comment, "comment5"}.  % ←これが有効
ct:log/[1,3]

ログをテスト結果(HTML 形式)に残す。
ct:log/3 は、第一引数にカテゴリ(atom 形式)を指定する。
カテゴリは、ログを囲むタグのクラス名として使われる。
ct:log/1 は、log(default, Format, []). と同義。

testcase1() -> [].
testcase1(_Config) ->
  ct:log("log1"),
  ct:log(test_category, "test category ~s", ["log2"]),
  ok.
ct:print/[1,3]

出力先がコンソール。
後は、ct:log/[1,3] と同じ。

ct:pal/3

ct:log/[1,3] と ct:print/[1,3] を足したもの。

Code Coverage Analysis

{level, details}.
{incl_mods, [foo]}.
% run_test -dir . -logdir ./log -cover ./config/foo.coverspec -pa `pwd`/ebin

続きは、また後で追加する。