Happy Testing Perl

第3回Test::Declareの紹介

第3回目を担当する小林です。 今回は私が開発したPerlモジュールのTest::Declareについて紹介します。

Test::Declareの特徴

Test::Declareには、以下の特徴があります。

  • テストコードをDSL風に(宣言的に)書く事を重点に置いている
  • それによりテストコード自体の見やすさを向上させることができる
  • 宣言的にテストを書く事により、一体なにを目的としたテストなのかがわかりやすくなる

Test::Declareでは、Test::More、Test::Deep、Test::Exception、Test::Warn、Test::Outputのfunctionをexportしているので、Test::Declareを1つuseするだけで、これらのモジュールのfunctionが利用可能となります。 これらのテストモジュールが提供するfunctionは通常よく使うものですので、毎回すべてをuseしなくても利用できるようにしてあります。

Test::Declareの使用例

Test::Declareを利用すれば、テストコードの見やすさが向上すると言いましたが、たとえばTest::Moreのfunctionを使ったテストの例として、Data::ObjectDriverで書かれているテストを取り上げてみたいと思います。

Data::ObjectDriverは、Six Apartで開発されているORマッパーで、CPANにもアップされているモジュールです。そのモジュールのsvn HEADの11-sql.tというテストコードでは以下のような記述となっております。

use Data::ObjectDriver::SQL;
use Test::More tests => 64;

my $stmt = ns();
ok($stmt, 'Created SQL object');

## Testing FROM
$stmt->from([ 'foo' ]);
is($stmt->as_sql, "FROM foo\n");

$stmt->from([ 'foo', 'bar' ]);
is($stmt->as_sql, "FROM foo, bar\n");

## Testing JOINs
$stmt->from([]);
$stmt->joins([]);
$stmt->add_join(foo => { type => 'inner', table => 'baz',
                        condition => 'foo.baz_id = baz.baz_id' });
is($stmt->as_sql, "FROM foo INNER JOIN baz ON foo.baz_id = baz.baz_id\n");

$stmt->from([ 'bar' ]);
is($stmt->as_sql, "FROM foo INNER JOIN baz ON foo.baz_id = baz.baz_id, bar\n");

#nop...

sub ns { Data::ObjectDriver::SQL->new }

ぱっとみで一体なにをテストしているのかわかりにくくないでしょうか?

そこでTest::Declareの出番です。Test::Declareを使うと、このような形で書くことができます。

use Data::ObjectDriver::SQL;
use Test::Declare;
plan tests => blocks;

my $stmt;
sub ns { Data::ObjectDriver::SQL->new }
sub reset_sql {
   $stmt->from([]);
   $stmt->joins([]);
}

describe 'Create SQL object' => run {
   test 'DOD::SQLのオブジェクトが作れる事' => run {
       ok ns;
   };
};

describe 'Testing FROM' => run {
   init {
       $stmt = ns;
   };
   test 'fooをfrom句に設定できること' => run {
       $stmt->from([ 'foo' ]);
       is $stmt->as_sql, "FROM foo\n";
   };
   test 'foo barをfrom句に設定できること' => run {
       $stmt->from([ 'foo', 'bar' ]);
       is $stmt->as_sql, "FROM foo, bar\n";
   };
   cleanup {
       reset_sql;
   };
};

describe 'Testing JOINs' => run {
   init {
       $stmt = ns;
   };
   test 'fooとbazをJOINできること' => run {
       $stmt->add_join(foo => { type => 'inner', table => 'baz',
                               condition => 'foo.baz_id = baz.baz_id' });
       is $stmt->as_sql, "FROM foo INNER JOIN baz ON foo.baz_id = baz.baz_id\n";
   };
   test 'barをfrom句に追加できること' => run {
       $stmt->from([ 'bar' ]);
       is $stmt->as_sql, "FROM foo INNER JOIN baz ON foo.baz_id =
baz.baz_id, bar\n";
   };
   cleanup {
       reset_sql;
   };
};

元の例よりもいくらか何をやっているテストなのかわかりやすくなったのではないでしょうか? 通常のPerlのコードと比べて、変わった書き方をしていると思いますが、もちろんPerlのコードです。

Test::Declareにおけるテスト件数

Test::Declareを利用すると、このようにこのブロックではどのようなテストを実行したいのかをかくことが簡単になります。

Test::DeclareはTest::Moreをベースにより使いやすいようにラッピングしているモジュールですので、Test::Moreのときに設定していた

plan tests => 1;

といった、そのテストスクリプト内のテスト件数を設定する必要があります。 しかし、Test::Moreの例の用にテスト件数をハードコーディングしてしまうと、テストを追加したときなどに、テスト件数をインクリメントする必要が発生してしまいます。

これが意外に面倒で、テスト件数を変更するのを忘れる事が多々あります。また、テストの件数を数えて設定しなければならずテスト本来の目的からはずれてしまいます。 例に挙げた11-sql.tでは64と数字を設定していますが、

use Test::More tests => 64;

これでは、テストケースを追加するたびに件数を数えて設定し直す必要があります。 テスト件数の少ないコードの場合は件数もすぐに数えられますが、11-sql.tのように1つのテストコード内のテスト件数が64件まで存在すると、テストコードをごりごり変更した場合、再度件数を数えなければならなくなり非効率です。 Test::DeclareではPPIというPerlモジュールを利用して、テスト実行時にテストスクリプト内にあるテストfunctionの件数を取得するfunctionを用意しましたこれにより、テストスクリプトを始めに書いたときに、

plan tests => blocks;

と一度設定すれば、あとは自動でテスト件数を設定してくれるようになり、テスト件数をインクリメントするという無駄な作業をなくすことができます。この発想はTest::Baseから頂戴しました。

まとめ

Test::Declareを使うにあたっての構成としてはこのようになりますので参考にしてください。

plan tests => blocks;
describe 'テストする範囲など記述' => run {
   init {
       # DBのsetupなど、テストに必要な前処理を記述
   };
   test '実際にテストを記述 テストがこけた場合ここのメッセージが表示されます' => run {
       is foo, 'foo';
       is bar, 'bar', '個別にメッセージを設定する事も可能';
   };
   cleanup {
       # テスト時に発生したキャッシュや、データの変更などを
       # 元に戻す
   };
}

Test::DeclareではこのようにTestのためのフレームワークを提供します。 実際の使用例としては

を参考にしてみてください。

Test::Declareは弊社のテストスクリプトで多く利用しているモジュールですが開発自体はcodereposで行っています。

現時点ではTest::WWW::Declareほどdeclarativeではありませんが今後改良を加える予定です。もし興味のある方がいらっしゃったらcodereposで一緒に開発しましょう! それでは、Happy testing!

おすすめ記事

記事・ニュース一覧