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
続きは、また後で追加する。