前回の(1)はこちら から。
テストモジュールの作り方
たくさんのテストを書いていくと、もっとアプリケーションの要件に合わせたテスト関数が欲しくなるときがあります。そんなときは、要件に合わせた独自のテストモジュールを作ることで解決できます。今回は、テストモジュールの作り方を解説します。
テスト関数を関数内から呼び出すと期待した動作とならない
実際のテストでは、テストのサンプルコードに書かれているような、1つの関数やメソッドを呼び出して結果を確認するといった単純なものにはなりません。テスト対象のメソッドを動作させるために必要なオブジェクトの生成や、データベースのセットアップ、テスト後のデータの削除など、さまざまな事前処理や事後処理のコードを書く必要があります。これらのコードが増えてくるとテストの見通しが悪くなり、似たようなコードだらけになってしまいます。
そうなってくると、繰り返し出てくるセットアップとテスト関数をまとめて、独自の関数として定義しようとすることになりますが、実はうまくいきません。
たとえばJSONデータに指定したキーが存在することをテストするために、JSON::PP
のdecode_json
の呼び出しとis
関数をまとめてtest_json_key.t
を作ったとします(リスト2 ) 。
リスト2 test_json_key.t
1: use Test::More tests => 1;
2: use JSON::PP;
3: sub test_json_key {
4: my ($json, $key, $val, $name) = @_;
5: my $ref = decode_json($json);
6: return is($ref->{$key}, $val, $name);
7: }
8: my $data = q#{ "address": "Gotanda" }#;
9: test_json_key(
10: $data, 'address',
11: 'Kichijoji',
12: 'address test'
13: );
リスト2を実行すると実際には9行目でテストが失敗がしますが、エラーメッセージにはFailed test 'address test' at test_json_key.t line 6.
と出力され、6行目でテストが失敗したと通知してきます。Test::More
にはis
を別の関数でラップしたことを知る手段がないためです。
Test::Builderを使って書きなおす
実はTest::More
をはじめとしたほとんどのテストモジュールは、Test::More
に付属するTest::Builder
というモジュールを使って作られています。先ほどの関数もTest::Builder
を使って書きなおすと正しい結果が得られます(リスト3 ) 。
リスト3 test_json_key_builder.t
1: use Test::More tests => 1;
2: require Test::Builder;
3: use JSON::PP;
4: sub test_json_key {
5: my ($json, $key, $val, $name) = @_;
6: my $ref = decode_json($json);
7: my $tb = Test::Builder->new;
8: return $tb->is_eq($json->{$key}, $val, $name);
9: }
10: my $data = q#{ "address": "Gotanda" }#;
11: test_json_key(
12: $data,
13: 'address',
14: 'Kichijoji',
15: 'address test'
16: );
リスト3の8行目で使用しているis_eq
はTest::Builder
が提供するテストメソッドで、Test::More
のis
関数と同じ機能です(実際、Test::More
のis
関数の内部では、このis_eq
を呼び出しています) 。
今度はエラーメッセージにFailed test 'address test' at test_json_key_builder.t line 11.
と出力され、正しく11行目が通知されます。
このように、定義した関数の中でTest::Builder
のメソッドを呼び出すことで独自のテスト関数を作ることができます。また、このように定義した関数をパッケージに入れればオリジナルのテストモジュールの完成です。
テストモジュールの定義
package Test::JSON::Key;
use parent qw/Exporter/;
our @EXPORT = qw/test_json_key/;
sub test_json_key {
...
}
独自のエラーメッセージを表示する
Test::Builder
にはok
メソッドという、真偽値とテスト名だけを引数に取る、とてもシンプルなテストメソッドが用意されています。テストが失敗すると必ず表示されるFailed [test name] at [test file]line [n].
というメッセージは、このメソッドが表示しています。
diag
メソッドと組み合わせて次のようにテスト関数を定義すれば、テストが失敗したときに独自のエラーメッセージを表示できます。たいていのテストモジュールではこのok
メソッドや、先ほどのis_eq
メソッドを内部で呼び出し、独自のテスト関数を定義しています。
テスト結果の判定と、表示
if ($pass) { # テストが成功したとき
$tb->ok(1, $name);
} else { # テストが失敗したとき
$tb->ok(0, $name);
$tb->diag("got: $got");
$tb->diag("expected: $expected");
}
テストの数を指定する
Test::More
ではテストの数をuse Test::More tests => 42
といった形式で指定できますが、独自のテストモジュールでも同じようにテストの数を指定したい場合は、Test::Builder
と一緒に提供されているTest::Builder::Module
を使います。
テスト数の指定
package Test::MyTestModule;
use parent qw/Test::Builder::Module/;
our @EXPORT = qw/my_test/;
sub my_test { ... }
このようにTest::Builder::Module
を継承すると、次のようにテストコード側でテスト数を指定できます(Test::More
自体もTest::Builder::Module
を継承しています) 。
use Test::MyTestModule tests => 42;
Test::Builder::Module
はExporter
モジュールを継承しているので、この場合は@EXPORT
で指定したmy_test
関数がエクスポートされ、かつテスト数が42となります。
Test::More
の解説でも触れましたが、テストを書くうえではdone_testing
を使って最後にテスト数をカウントする方法がお勧めですが、テストモジュールを作るうえではTest::Builder::Module
を使って両方のスタイルに対応できるようにしておくのが良いでしょう。
Test::Testerによるテスト結果のテスト
テストモジュールにも当然テストは必要です。テストの結果は標準出力と標準エラー出力にTAP形式で出力されるので先述したTest::Output
を使ってのテストもできますが、Test::Tester
というモジュールを使うともっと簡単にテスト結果のテストができます。
Test::Tester
は、Perl 5.22.0
よりコアモジュール入りしたテストモジュールです。Perl 5.22.0
より前のバージョンを使っている場合は、バージョン1.001010
以降のTest::More
をCPANからインストールすると使えるようになります。
Test::Tester
ではTest::Builder
の主要なメソッドを置き換え、テスト結果をオブジェクトとしてテストできるようになっています。たとえばis
関数の挙動をテストするテストはリスト4 のようになります。
リスト4 test_test_func.t
use Test::Tester;
use Test::More;
check_test(
sub {
is(1, 2, "failure test");
diag("test end");
},
{
ok => undef,
name => "failure test",
diag => " got: '1'\n" .
" expected: '2'\n" .
"test end"
},
'is test'
);
done_testing;
また、複数のテスト結果をまとめて取得するrun_tests
という関数も用意されています。
run_testsによる複数テストの実行
my ($premature, @results) = run_tests(
sub {
is(1, 1, "success test");
is(1, 2, "failure test");
}
);
is(
$result[0]->{name},
"success test",
"name test"
);
テストが開始される前に出力された文字列は$premature
に入り、各テストの結果が@results
に入ります。あとはリスト4のcheck_test
と同様にokや、name
、diag
といったハッシュキーで結果を取り出し、通常のテスト関数で結果をテストします。
このようにTest::Tester
を使えばテスト単位で結果がオブジェクトとして取り出せるため、より構造的に、整理されたテストが書きやすくなります。
<続きの(3)はこちら 。>