本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーは、
本稿のサンプルコードは、
Perlにおけるテスト
読者のみなさんはテストを書いていますか。
Perlは昔からテストをとても大事にしてきた言語です。テストを書くために必要なテストモジュールはコアモジュールとしてPerl本体と一緒にインストールされていて、
今回はテストモジュールの使い方や、
なお、Perl 5.
以降で動作するように書かれており、Perl 5.
で動作確認を行いました。
また、use strict
と、use warnings
はすべて省略していますが、
テストモジュールの使い方
まずはテストモジュールの使い方とテストの実行方法を解説します。
Test::More──Perlのテストの基本
テストモジュールの中で最も基本的な機能を提供するモジュールがTest::More
です。Test::More
はコアモジュールとしてPerl本体と一緒にインストールされています。
Test::Moreを使ったテスト
最初のテストとして、
1: use Test::More;
2: is(length("perl"), 4, "length test");
3: is(length(undef), 0, "undef test");
4: done_testing;
1行目は、Test::More
を使用するための宣言です。以降で使用するis
という関数が使えるようになります。
2行目は、Test::More
のis
関数を使ってテストを実行しています。is
関数は引数を3つ取り、length("perl")
の戻り値と、length test
を指定しています。
3行目では再びis
関数を使って今度はlength(undef)
と、0
を比較しています。なお、length(undef)
は実際にはundef
を返すので、
最後の4行目では、Test::More
のdone_
関数を呼び出しています。
テストの実行
リスト1を通常のPerlのプログラムと同じように実行してください。
$ perl length_test.t
ok 1 - length test ―(1)
not ok 2 - undef test ―(2)
# Failed test 'undef test' ┐
# at length_test.t line 3. |
# got: undef |―(3)
# expected: '0' ┘
1..2 ―(4)
# Looks like you failed 1 test of 2. ―(5)
テストの結果が出力されました。1行ずつ見ていきましょう。
ok
と出力されていますが、1
)is
に渡したテストの名前です。
not ok
と出力されていますが、
length_
)got
)expected
)
すべてのテストが終わりdone_
が実行されると、1..[テスト数]
という形式で出力されます。
失敗したテストがあるとテストプログラムが終了するタイミングで、
このようにPerlでは、is
関数は最も基本的なテストのための関数Test::More
が提供するそのほかの機能を解説します。
like──正規表現によるテスト
like
は正規表現を使うテスト関数です。引数の2番目に正規表現へのリファレンスを渡す点がis
と異なります。
like(
"perl-5.22.0.tar.gz",
qr/perl-5\.\d+\.\d+\.tar\.gz/,
"archive name test"
);
cmp_ok──任意の演算子を使った比較テスト
cmp_
は指定した演算子で比較を行うテスト関数です。数値の大小比較など、
cmp_ok(1 / 10, '<', 0.11, "division test");
is_deeply──リファレンスのテスト
is_
はリファレンス用のテスト関数です。2つのリファレンスをたどりながら比較を行います。
sub create_person {
return { name => $_[0], age => $_[1] }
}
is_deeply(
create_person( "James", "30" ),
{ age => "30", name => "James"},
"person object test"
);
リファレンスの先に一致しない箇所があるとテストが失敗し、
plan──テストの数を指定する
リスト1で用いたdone_
はすべてのテストを実行したあとにテストの数をカウントする関数ですが、plan
という関数で最初にテストの数を指定することもできます。
42個のテストを実行
plan tests => 42;
plan
を使わずに、Test::More
を読み込むときに指定することもできます。
# 同じく42個のテストを実行
use Test::More tests => 42;
テストの数と実際に実行したテストの数が不一致の場合、
以前はテストの数をあらかじめ指定する方法が主流でしたが、done_
を呼び出す方法がお勧めです。
subtest──テストをまとめる
テストをたくさん書いていくと、subtest
というしくみが用意されています。
subtest
は、
1: use Test::More;
2: subtest 'group1' => sub { is(1, 1);is(1, 1) };
3: subtest 'gourp2' => sub { is(1, 1);is(0, 1) };
4: done_testing;
実行すると、
# Subtest: group1
ok 1
ok 2
1..2
ok 1 - group1 ―(1)
# Subtest: gourp2
ok 1
not ok 2
# Failed test at subtest.t line 3.
# got: '0'
# expected: '1'
1..2
# Looks like you failed 1 test of 2.
not ok 2 - gourp2 ―(2)
# Failed test 'gourp2'
# at subtest.t line 3.
1..2
# Looks like you failed 1 test of 2.
先にコードリファレンスの中のテストが実行され、subtest
全体が成功となり、subtest
全体が失敗となります。
このようにsubtest
はテストのコードを理解しやすいように階層化するために使いますが、
diag──解析用メッセージを出力する
テスト関数が失敗すると原因が出力されますが、diag
関数を使用します。
is($got, $expcted) or diag("DB:$db, USER:$user");
テストに失敗するとテスト関数自体の戻り値は偽になるので、DB
とUSER
の情報が出力されます。
diag
関数の出力はすべて#
が先頭に付加され、
BAIL_OUT──テストを中断する
BAIL_
は、
BAIL_OUT "abort the test";
BAIL_
が呼び出されると、BAIL_
以降のコードはすべて無視されます。
Bail out! abort the test
テスト結果の出力はTAP形式
リスト1で、ok
、not ok
が出力されると説明しましたが、
- テストが成功すれば
ok
、失敗すれば not ok
を出力する ok
とnot ok
に続けて、テストの連番とテスト名を出力してもよい (出力しなくてもよい) - テストの前か一番最後で、
1..[連番]
という形式でテストの数を出力する - テストが継続できないときは
Bail out!
と出力し、ただちに実行を中断する #
で始まる行は、エラー内容の詳細などテスト結果を補足する情報を出力する - 上記以外の出力内容は、
TAPとしてはすべて無効となる (テストの結果には影響しない)
Perlでは、prove
コマンドでテスト結果を収集、
なお、Wide character in print...
という警告メッセージが出力されます。これを防ぐためには次のように、Test::More
を読み込む前に明示的に標準出力や標準エラー出力のエンコーディングを指定します。
use open ':std', ':encoding(utf8)';
use Test::More;
テストを実行し結果を収集するproveコマンド
テストを一つ一つ実行し、prove
というコマンドが用意されています。
リスト1をprove
コマンドで実行してみます。
$ prove length_test.t
length_test.t .. 1/?
# Failed test 'undef's length test'
# at length_test.t line 3.
# got: undef
# expected: '0'
# Looks like you failed 1 test of 2.
length_test.t .. Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/2 subtests
Test Summary Report
-------------------
length_test.t (Wstat: 256 Tests: 2 Failed: 1)
Failed test: 2
Non-zero exit status: 1
...
Result: FAIL
1つでもテストが失敗すると、Result: FAIL
と表示されるので、
次に、is(length(undef), undef,"undef test");
と書き換え、
$ prove length_test.t
length_test.t .. ok
All tests successful.
...
Result: PASS
TAPの詳細な出力はすべて省略され、
なお、t
というディレクトリにテストを置いておけば、prove
のみでまとめて実行してくれます。
次のように明示的にディレクトリの指定もできます。
$ prove testdir/
このようにprove
コマンドを使うことで、
そのほかのテストモジュール
Test::More
以外にも、Test::Exception
や、Test::Output
など、
use Test::Exception;
throws_ok { die "exception msg\n" } qr/exception/;
use Test::Output;
stdout_is sub { print "Hello, World" }, "Hello, World";
テストモジュールを探したいときは、awesome-perl
やTask::Kensho::Testing
などを参照すると、
ここまでのまとめ
(1)Test::More
、prove
の3つを覚えればテストを始めることができますので、
<続きの