てか、自分も調べながら書いたんで、間違ってたら教えてください。
Test::Baseとは何か
Test::Baseというのは、要するにデータドリブンなテストを書くためのフレームワーク(?)のこと。Test::SimpleとかTest::Moreでクドクド書いていたテストをもっと簡単に書けるようにしましょう、と。
基本的にテストというものは、入力値に対してサブルーチンが期待される値を返すかをチェックするというのが主な流れであるわけで、逆に言えばその流れでチェックできる関数がよい関数だと言える。より関数的な関数というか。
だから、処理指向でチェックすべき内容をグダグダとコーディングすると、見にくいしメンテしにくいテストコードが出来上がる。
Test::Baseの概要をおおざっぱに述べておくと、スクリプトの__DATA__セクション(≒__END__行より後の部分)に
- 入力する値 (input)
- 期待される値 (expected)
なデータをつらつら書くことによって、それに沿って(半)自動的にテストを行なってくれるというもの(もちろん動作はいろいろ変えることが出来るんだけど)。
実際に使ってみる
さて、実際にTest::Baseを使ってDDなテストを書いてみることにする。
今回はテストを書くことが目的なので、コードの方は下らないものを書いてみることにする。例えばこんなの。
# Calc.pm
package Calc;
use strict;
use warnings;
use version;
use base qw(Exporter);
our @EXPORT = qw(calculate);
our $VERSION = qv('0.0.1');
use Switch;
# 第1引数に、'+', '-'などの演算子が渡され、第2,3引数の値で計算して返す
sub calculate {
my $ope = shift;
switch($ope) {
case '+' { return do_plus(@_); }
case '-' { return do_minus(@_); }
else { return 'unknown operator' }
}
}
sub do_plus {
return $_[0] + $_[1];
}
sub do_minus {
return $_[0] - $_[1];
}<Excuse>本来ならテストを先に書くのだ、という突っ込みは今回は無しで。</Excuse>
で、テストコードの方は、まずはこのアプリケーション(?)に対してテストクラスを作る。
# TestCalc.pm package TestCalc; use Test::Base -Base; use Calc; 1;
今回は初歩なので、たったのこれだけ。
そして、*.t ファイル。
# t/plus.t
# 足し算を検査する
use TestCalc;
plan tests => 1 * blocks;
run {
my $b = shift;
is( Calc::calculate('+', split(/\s/, $b->input) ),
$b->expected);
};
__END__
=== Test 1
--- input
1 2
--- expected chomp
3
=== Test 2
--- input
-1 3
--- expected chomp
2さて、ここまでやってから、コマンドラインで
perl -MExtUtils::Command::MM -e "test_harness()" t/*.t
とでもやってみよう。ちなみにこれはaliasしておくと便利。
自分の環境(Debian Sarge + Perl 5.8.4)では、次のようにテストをpass.
t/plus....ok All tests successful. Files=1, Tests=2, 1 wallclock secs ( 0.46 cusr + 0.69 csys = 1.15 CPU)
説明
さて、上記のスクリプトの説明を軽くしておくと、TestCalc.pmはとりあえず騙されたと思って書いておく。自分で filter を作るときはもうちょっと書かなければいけないのだけれど、それについては次回(があれば)書くことにして、注目は t/plus.t。
まず、テストは run()関数によって起動される*1のだけれど、これにはBlockというものが渡される。これは、__END__以降に書かれた、
=== Test 1 --- input 1 2 --- expected chomp
という部分のこと。今回のテストでは、このクロージャは計2回呼ばれることになる。
ブロックの記述の仕方は、
=== ブロック名(テスト名) --- データセクション名 データ データ データ : --- データセクション名 データ データ データ :
という形。そして、データセクション名はBlockオブジェクトのメソッド名として呼ぶことができて、中身を取得できる。
つまり、上記のrun()では、'input'セクションのデータを取り出し、それを split して、calculate()に渡して和を計算し、expected セクションの値と比較している。これでめでたくテストができた。パチパチ。
'---expected'の後に'chomp'ってのがあって、これは定義済み filter なんだけど、これについては次回に譲って割愛。
*1:一応書いておくと、テストの起動の仕方にもいろいろある