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

第29回 Test::Base:データ本位のテストをするときは

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

データファイルを利用するテスト

ファイル探索型のテストはテスト対象のモジュールが増えてもコードを書き換える必要がない点ではすぐれていますが,バージョン管理ツールのデータや作業中のゴミファイルまで拾ってしまいかねないのが泣き所でした。そのため,これらのテストにはしばしば条件に一致しないファイルをスキップするような仕掛けが用意されているのですが,そのような条件はわざわざテストごとに指定しなくても,たとえばバージョン管理システムの除外ファイルや,MANIFEST.SKIPのようなツールチェーン側で用意している既存のファイルを利用すればDRYに設定できるはずですし,ディストリビューションに含まれるファイル自体,わざわざディレクトリを再帰的に検索していかなくても,ディストリビューションを作成するときに生成されるMANIFESTファイルを使えば必要なファイル情報を得られるはずです。

このように既存のデータファイルを利用して件数を調べるモジュールとしては拙作のTest::UseAllModulesなどがありますが,この手のデータファイルを利用したテストはむしろ特定のサイトが正しい反応を返しているかを確認する簡単な監視ツールなどで見かけることのほうが多いかもしれません。たとえばこのようなスクリプトを用意してcrontabで定期的に実行しておけば,監視しているサイトに異常が起きているときだけテスト結果をcrontabに登録したメールアドレスに通知することができます※2⁠。

use strict;
use warnings;
use Test::More;
use LWP::UserAgent;
use HTTP::Status 'status_message';
use YAML::Tiny;

my $conf = shift or die "USAGE: prove $0 :: <config>.yaml > /dev/null";
my @urls = @{ YAML::Tiny::LoadFile($conf) };

plan tests => scalar @urls;

my $ua = LWP::UserAgent->new(timeout => 3);
for my $url (@urls) {
    my $res = $ua->head($url);
    ok $res->is_success or diag "$url: ".status_message($res->code);
}
※2

ここでは正常系の出力を/dev/nullに捨てることを前提にしていますが,奥一穂氏作のcronlogのようなツールを使えばproveの正否に応じてもう少し柔軟な運用を行うこともできるでしょう。より本格的な監視を行いたい場合はNagios等の専用ツールを使ってください。

Test::Base

インギー・ドット・ネット氏が2005年の春にリリースしたTest::Baseは,このようなテスト対象のデータ化をさらに押し進めたものといえます。

当初はTest::Chunksという名前で公開されていたこのモジュールは,もっとも狭い意味では渡されたデータを別の形に変換して出力するフィルタのようなプログラムをテストするためのものですが,一般的にはもう少し広い意味でとらえて,何らかの引数を与えたら何らかの値が返ってくる,あらゆるタイプの関数やモジュール,アプリケーションのテストに利用されています。

たとえば,特定のサイトに特徴的な文字列が含まれているか確認したいとしましょう。ふつうならURLを渡してコンテンツを取得し,そのコンテンツに対して正規表現がマッチするかどうかを確認する,というテストをループで回していくところですが,Test::Baseを使うと,たとえばこのように書くことができます。

#!perl
use strict;
use warnings;
use Test::Base;
use LWP::Simple;

plan tests => scalar blocks;

sub url    { get($_) }
sub regexp { qr/(?six:$_)/ }

run_like 'input' => 'expected';

__DATA__
=== search.cpan.org
--- input url
http://search.cpan.org/
--- expected regexp
<title>The[ ]CPAN[ ]Search[ ]Site.*</title>
=== gihyo.jp
--- input url
http://gihyo.jp/dev/serial/01/modern-perl/
--- expected regexp
<title>.+gihyo\.jp.+</title>

Test::Baseは裏でソースフィルタが走っていることを含めてさまざまな仕掛けが施されているので通常の書き方とはやや異なる部分もありますが,何をしているかを読みとること自体はそれほどむずかしくはないでしょう。ここでは「__DATA__」以下にある「===」「---」で区切られたテキストがひとつひとつのテストブロック/データになっていて,search.cpan.orgとgihyo.jpのそれぞれについて,input以下のブロックの内容をurlというフィルタに通した結果が,expected以下のブロックの内容をregexpというフィルタに通した結果得られた正規表現にマッチするかどうかをテストする,という仕組みになっています。

テストブロックの数はblocksというコマンドの返り値を調べればわかるので(実際にはこのコマンドはすべてのブロックを返すのに使われます⁠⁠,ここではあえて明示的にスカラー化したものをテストの数として宣言していますが,Test::Baseではplanの行もrun_likeの行も特に必要なければ省略できます(ついでに言うとuse strictとuse warningsの行も省略できますが,筆者は面倒でも省略せずに書いておくほうが好きです⁠⁠。

また,ここではテストに直接フィルタとなるコードを書いていますが,コードの内容が複雑だったりほかのテストでも再利用したい場合は,フィルタ用のライブラリを別に用意したほうがよいでしょう。上の例であれば,たとえばこのような内容のMyTest.pmというモジュールを用意して,

use strict;
use warnings;

package MyTest;
use Test::Base -Base;

package MyTest::Filter;
use Test::Base::Filter -base;
use LWP::Simple;

sub url    { get($_[0]) }
sub regexp { qr/(?six:$_[0])/ }

1;

テストスクリプトからこのように呼び出すと,同じ動作を期待できます(ここではurlやregexpの引数が変わっていることに注意してください。フィルタをテストスクリプトの中に書いた場合は$_を引数にすることができましたが,外部のモジュールに移した場合その書き方ではうまくいきません⁠⁠。

#!perl
use strict;
use warnings;
use MyTest;

__DATA__
=== search.cpan.org
--- input url
http://search.cpan.org/
--- expected regexp
<title>The[ ]CPAN[ ]Search[ ]Site.*</title>
=== gihyo.jp
--- input url
http://gihyo.jp/dev/serial/01/modern-perl/
--- expected regexp
<title>.+gihyo\.jp.+</title>

より高度な使い方を知りたい方は一世を風靡したPlaggerのテストコードがTest::Baseを多用しているので参考にしてみるとよいでしょう。PlaggerのテストではinputブロックにYAMLの設定を渡して,expectedブロックのほうにはよくあるTest::Moreのコマンドを並べる(これらは微調整したうえで最終的にはevalで評価されます)といった書き方もできるようになっています。

さらにテストの再利用性を高めるために

Test::Baseのようなデータ本位のテストは簡単で便利ですし,フィルタ部分を外部モジュールにすることでテストコードの再利用性も高くなっていますが,テストの種類によってはシリアライズしづらい(あるいは余計な手間がかかる)こともあります。たとえば,ひとつの入力に対して複数のテストを行いたい場合(たとえば上のテストで,コンテンツだけでなく,ヘッダやステータスコードのテストも同時に行いたい場合⁠⁠,同じコンテンツを何度も取りに行くのは無駄ですし,テストの性質を考えても適切とはいえません(コンテンツの取得はかならずしも成功するとは限りませんし,成功したときのコンテンツ,ヘッダ,ステータスコードの組み合わせと,失敗したときの組み合わせを知りたい場合など,毎回コンテンツを取りに行くのではテストの意図が変わってしまいます⁠⁠。

次回はそのような場合にも対応できるフレームワークを見ていきます。

著者プロフィール

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

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

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