モバイルファクトリーの伏原です。 私は今回Test::Baseというモジュールを紹介させてもらいます。
Test::Baseとは
Test::Baseは、
今回使うサンプルプログラム
NabeAtzz問題を解くプログラムを作ります。NabeAtzz問題にはいくつかのバリエーションがありますが、
- 1からnまでの整数に対して
- 3の倍数の時は
“fool” と出力する - 5の倍数の時は
“dog” と出力する - いずれかの桁に3が含まれる整数の時も
“fool” と出力する - それ以外の数の場合は整数を
“[1]” のように[]で囲って出力する
これを満たすために、
#!/usr/bin/perl -Ilib use strict; use warnings; use NabeAtzz; for my $n (1..$ARGV[0]) { print NabeAtzz->shout($n) . "\n"; }
ファイル作成後、
./nabeatzz.pl 20
のように実行すると、
Test::Baseを使ったテストの書き方
さて、
use strict; use warnings; use Test::Base; use NabeAtzz; plan tests => 3; is(NabeAtzz->shout(1), '[1]' ); is(NabeAtzz->shout(4), '[4]' ); is(NabeAtzz->shout(19), '[19]');
まだNabeAtzz.
package NabeAtzz; use strict; use warnings; sub shout { my ($class, $number) = @_; return "[$number]"; } 1;
改めて以下のようにテストを実行すると、
> perl -Ilib t/nabeatzz.1.t 1..3 ok 1 ok 2 ok 3
Test::Baseを使ったテストのスタイル
nabeatzz.
use strict; use warnings; use Test::Base; use NabeAtzz; plan tests => 1*blocks; filters { input => [qw/chomp/], expected => [qw/chomp/], }; run { my $block = shift; is(NabeAtzz->shout($block->input), $block->expected); }; __END__ === test 1 --- input 1 --- expected [1] === test 4 --- input 4 --- expected [4] === test 19 --- input 19 --- expected [19]
スクリプトの行数自体は長くなってしまいましたが、
Test::Baseの記述方法
データ
Test::Baseでは、
=== テスト名(省略可能)
という行で区切られていて、
--- フィールド名 値
という形で入力していきます。ここは、
--- フィールド名: 値
という書き方もできますが、
--- フィールド1: 値 --- フィールド2: 値 --- フィールド3: 値
のようになって行が詰まって見辛くなることがあるので、
テストプラン
普通のテストの場合、
plan tests => 10;
などと記述します。しかし、
use Test::Base; plan tests => 1 * blocks;
blocksは、
plan tests => 1 * 3
と同義ということになります。
フィルタ
入力値、
filters { input => [qw/chomp/], expected => [qw/chomp/], };
filters { に続けて
filters({ input => [qw/chomp/], expected => [qw/chomp/], });
このように、
- yaml
- フィールドデータとして複雑な値
(ネストしたハッシュなど) を渡す場合、 YAML形式でデータを記述し、 このフィルタを通すとperlのデータに変換されます - eval
- フィールドデータをperlスクリプトとみなして実行し、
結果を値とします - trim
- データ前後の空白をカットします
他にも、
run
run { ... } の中で実際のテストの処理を記述することになります。この部分もfiltersの時と同じく実質はrun関数を呼び出しているので、
=== テスト名
で記述したテスト名
run { my $block = shift; is(NabeAtzz->shout($block->input), $block->expected); };
フィルタの独自定義とrunの簡略化
フィルタは、
sub nabeatzz { NabeAtzz->shout(shift) }
このように書くと、
sub nabeatzz { NabeAtzz->shout(shift) } filters { input => [qw/chomp nabeatzz/], expected => [qw/chomp/], }; run { my $block = shift; is $block->input, $block->expected; };
変更したテストも、
perl -Ilib t/nabeatzz.3.t 1..3 ok 1 ok 2 ok 3
実は、
run_is input => 'expected';
run_
run_is
と単純に書けます。
筆者の場合、
run_is_deeply
基本的にはrun, run_
run_is_deeply; __END__ === --- input 1 --- expected data: foo: bar
テストケースの追加
ここまでしっかりとロジック側を設定すれば、
=== test 10 ( multiple of five --- input 10 --- expected dog === test 20 ( multiple of five --- input 20 --- expected dog
プログラムを変更せずにテストを実施すると、
> perl -Ilib t/nabeatzz.4.t 1..5 ok 1 ok 2 ok 3 not ok 4 # Failed test at t/nabeatzz.4.t line 21. # got: '[10]' # expected: 'dog' not ok 5 # Failed test at t/nabeatzz.4.t line 21. # got: '[20]' # expected: 'dog' # Looks like you failed 2 tests of 5.
このように、
Test::Baseを使う上でのtips
詳しくはperldoc Test::Baseで直接参照してもらうのが一番良いですが
テストデータの外部読み込み
spec_file($filepath);
上記を実行すると、
テストデータ中でのフィルタの指定
--- input chomp nabeatzz 1
上のように記述すると、
SKIP, ONLYフィールド
=== test --- ONLY --- input 1
ONLYという名称のフィールドがあった場合、
完成したNabeAtzz.pmとそのテスト
最後に、
package NabeAtzz; use strict; use warnings; sub shout { my ($class, $number) = @_; return "fool" if ($number % 3 == 0) or $number =~ /3/; return "dog" if $number % 5 == 0; return "[$number]"; } 1;
use strict; use warnings; use Test::Base; use NabeAtzz; plan tests => 1*blocks; filters { input => [qw/chomp nabeatzz/], expected => [qw/chomp/], }; sub nabeatzz { NabeAtzz->shout(shift) } run { my $block = shift; is_deeply $block->input, $block->expected; }; __END__ === test 1 --- input 1 --- expected [1] === test 4 --- input 4 --- expected [4] === test 19 --- input 19 --- expected [19] === test 10 ( multiple of five --- input 10 --- expected dog === test 20 ( multiple of five --- input 20 --- expected dog === test 3 ( multiple of three --- input 3 --- expected fool === test 9 ( multiple of three --- input 9 --- expected fool === test 43 ( three is contained --- input 43 --- expected fool === test 301 ( three is contained --- input 301 --- expected fool
まとめ
今回はTest::Baseについて、
それでは、