モダンPerlの世界へようこそ

第27回 Test::Most:Test::Moreでは物足りなくなってきたら

この記事を読むのに必要な時間:およそ 4 分

Test Anything Protocol

Perlは非常にテストを重視している言語です。連載第14回ではPerl本体のテスト数がどのように推移してきたかを,また連載第24回ではCPANモジュールの品質保証に大きな役割を果たしてきたCPANTSについて簡単に紹介しましたが,Perlとテストのつながりはそれだけではありません。CPANにはTestを名前に含むディストリビューションが500以上もあがっていますし(これは全ディストリビューション数の約2.5%にあたります)⁠Perlで標準的に使われているテスト形式はTest Anything Protocol (TAP)という名前を得て多くの言語に移植され,2008年からはIETFの標準化を目指した活動も始まっています――というと何やらすごいプロトコルのように聞こえるかもしれませんが,Test Anything Protocolというのは要するに最初にテストの個数を宣言し,テストが成功したら「ok (テスト番号)」を,テストが失敗したら「not ok (テスト番号)」を出力する,というだけのもの。だから,理屈がわかっていればprint文だけでも書けてしまいます。

たとえば,テストに使われているperlのバージョンが5.6以上かどうかを確認したいとしましょう。perlのバージョンは$]で取得できるので,このように書いておけばTAP準拠のテストができたことになります。簡単ですね。

#!perl
use strict;
print "1..1\n";
print $] >= 5.006 ? "ok 1\n" : "not ok 1\n";

もっとも,ひとつくらいならこのように手書きしてもかまいませんが,テストの項目数が増えたらとてもこのような書き方はしていられません。そのため,ふつうはモジュールの力を借りるのですが,さてこのとき,みなさんならどう書きますか,というのが今回のお題目。

Test::SimpleとTest::More

おそらく多くの方が真っ先に思いつくのはこのような書き方でしょう。

#!perl
use strict;
use Test::More tests => 1;
ok($] >= 5.006);

上の例とほぼ一対一対応していますから特に説明は不要と思いますが,use Test::MoreでTest::Moreモジュールを呼び出し,そのときにテストの数も指定して,実際のテストはokでくくる。これでテストが通ればokが出力され,テストに失敗すればnot okが出力されるようになるわけですが,残念ながら,これではTest::Moreを十分に使いこなしているとはいえません。これはTest::Moreより単純なTest::Simpleの書き方だからです。

#!perl
use strict;
use Test::Simple tests => 1;
ok($] >= 5.006);

もう少しTest::Moreらしい書き方をすると,こうなります。

#!perl
use strict;
use Test::More tests => 1;
cmp_ok($], '>=', 5.006);

両者の違いはテストに失敗したときの出力にあるのでした。ためしに不等号の向きを反対にして,結果を確認してみましょう。Test::Simple風のokを使ったテストはこのようにテストの正否しかわかりませんが,

> prove test_ok.t
test_ok.t ..
test_ok.t .. 1/1 #   Failed test at test_ok.t line 4.
# Looks like you failed 1 test of 1.
test_ok.t .. Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/1 subtests

cmp_okを使うと,いちいちデバッグ用の出力を用意しなくても,失敗したときにはどう失敗したのかがコメントとして出力されます。

> prove test_cmp_ok.t
test_cmp_ok.t ..
test_cmp_ok.t .. 1/1 #   Failed test at test_cmp_ok.t line 4.
#     '5.008009'
#         <=
#     '5.006'
# Looks like you failed 1 test of 1.
test_cmp_ok.t .. Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/1 subtests

もちろんこのような情報はテストの説明文のなかに埋め込むこともできます(この場合はproveコマンドに-vオプションを渡せばテストの結果によらず表示されます)⁠

#!perl
use strict;
use Test::More tests => 1;
ok($] >= 5.006, "Perl version: $]");

また,一般にテスト関数はテストが失敗したときに偽を返すので,このように書いてもよいでしょう(この場合はcmp_okと同じくテストが失敗したときにしか表示されません)⁠

ok($] >= 5.006) or diag "Perl version: $]";

cmp_okの書き方はいささか冗長になるため,つい横着をしてokで書きがちですが,テストがこけるたびにwarnなどを埋め込んで値を調べるくらいなら,最初からcmp_okなどを使う方がスマートです。Test::Moreのコマンドを使えば失敗したテストがどのファイルのどの行にあるか表示されるので,いちいち説明文を書かなくてもそれほど困らない,というのもメリットといえるかもしれません(もちろんテストの意図を明確にするうえでは適切な説明文を書いておいた方が流れを追いやすくなります)⁠

Test::Moreで利用可能なテスト

Test::Moreにはcmp_okのほかにもいくつかのテスト関数が用意されています。たとえば,ふたつの値が一致するかどうかを調べるときはcmp_okよりも簡単なisを,一致しないことを確認するときはisntを使うのでした。このような場合は「,」を使うより「=>」を使う方がいくぶんわかりやすくなる,というのも多くの方がご存じのことでしょう。

is(  $] => 5.006);
isnt($] => 5.006);

正規表現の比較も,likeやunlikeを使うと,失敗したときにどのような正規表現と比較しようとしていたかがわかるので便利です。

like(  $] => qr/5\.006/);
unlike($] => qr/5\.006/);
> prove test_like.t
test_like.t ..
test_like.t .. 1/1 #   Failed test at test_like.t line 4.
#                   '5.008009'
#     doesn't match '(?-xism:5.006)'
# Looks like you failed 1 test of 1.

リファレンスを使った複雑なデータはis_deeplyで再帰的にテストできるのでした。

is_deeply({ foo => 'bar' }, { foo => 'baz' });
> prove test_deeply.t
test_deeply.t ..
test_deeply.t .. 1/1 #   Failed test at test_deeply.t line 4.
#     Structures begin differing at:
#          $got->{foo} = 'bar'
#     $expected->{foo} = 'baz'
# Looks like you failed 1 test of 1.

Test::Moreにはほかにも,モジュールが正しくロードできるか確認するuse_okやrequire_ok,メソッドが利用できるかどうかを確かめるcan_ok,継承関係を確認するisa_okといったテストが用意されています。これまで使ったことがなかった方はぜひ一度Test::MoreのPODをご確認ください。

著者プロフィール

石垣憲一(いしがきけんいち)

あるときは翻訳家。あるときはPerlプログラマ。先日『カクテルホントのうんちく話』(柴田書店)を上梓。最新刊は『ガリア戦記』(平凡社ライブラリー)。

URLhttp://d.hatena.ne.jp/charsbar/

コメント

コメントの記入