Test::Class

最近 Perl でテストを書くときに Test::Class を使ってます。(もしかして常識?) これまでは *.t で Test::More をそのまま使ってたけど、テストが大きくなってくるとコードが分かりにくくなったり、自分であれこれしなきゃいけないことが多くてめんどくさい。

Test::Class は xUnit スタイルで Perl のテストを書けるフレームワークです。xUnit な Perl 実装といえば Test::Unit もあるんですが、テスト用の関数も Test::Unit の流儀に従う必要があってちょっと嫌。Test::Class は Test::More と Test::Harness とか、普段使い慣れてる Perl らしいテストスタイルを使いつつ xUnit できるという点が良いです。

使い方ですが、

  • Test::Class を継承したテストクラスを作り
  • テスト用のメソッドをメソッドアトリビュートで指定しつつ作っていき
  • テストクラスの runtests を呼ぶ

という流れ。

例えば Dog クラスの bark メソッドをテストをするには、t/01-dog.t として

#!perl
use strict;
use warnings;

My::Test->runtests;

package My::Test;
use base qw/Test::Class/;
use Test::More;
use Dog;

sub test_bark : Test {
    my $dog = Dog->new({ name => 'pochi', age => 18 });
    is $dog->bark, "bowwow";
}

1;

などと書きます。ここではテストの実行ファイルの中にクラス定義を書いてますが、普通にライブラリパス切ってその中に入れても OK。

テストは sub test_bark : Test { ... } の中に書きます。Test アトリビュートをつけたメソッドが、runtests 時に呼ばれてテストされます。メソッドが呼ばれる順はランダム。テストメソッドの中では、Test::More の is を使ってテストしてます。

もう少しテストを拡充してみます。

package My::Test;
use base qw/Test::Class/;
use Test::More;
use Dog;

sub make_fixture : Test(setup) {
    my $self = shift;
    $self->{dog} = Dog->new({ name => 'pochi', age => 18 })
}

sub test_instance : Test(6) {
    my $dog = shift->{dog};
    isa_ok $dog, 'Class::Accessor::Fast';
    isa_ok $dog, 'Dog';
    ok $dog->can($_) for qw/name age/;
    is $dog->name, 'pochi';
    is $dog->age, 18;
}

sub test_bark : Test {
    my $dog = shift->{dog};
    is $dog->bark, "bowwow";
}

1;

Test アトリビュートをつけたテストメソッドをつけた中で実行するテストの数がひとつの場合は、Test で ok ですが、複数になるときはその数を Test(4) などと書きます。もちろん Test(no_plan) とも書けます。Tests でもいいらしい。

その他特殊なアトリビュートとしては setup / teardown / startup / shutdown などがあります。説明の必要はないでしょう。ここでは setup を使ってインスタンス生成をやってます。

テストをするときは prove を使うと楽。*1lib/Dog.pm に Dog の定義をしているとして

% prove -vl t/01-dog.t
t/01-dog....ok
All tests successful.
Files=1, Tests=7,  0 wallclock secs ( 0.13 cusr +  0.03 csys =  0.16 CPU)

という具合で実行できます。prove -v すると、

% prove -vl t/01-dog.t
t/01-dog....#
# My::Test->test_bark
1..7
ok 1 - test bark
#
# My::Test->test_instance
ok 2 - The object isa Class::Accessor::Fast
ok 3 - The object isa Dog
ok 4 - test instance
ok 5 - test instance
ok 6 - test instance
ok 7 - test instance
ok
All tests successful.
Files=1, Tests=7,  0 wallclock secs ( 0.12 cusr +  0.05 csys =  0.17 CPU)

という感じで、どのテストを実行してるかも出力してくれて便利です。

Test::Class には他にもいろいろ機能がありますが、基本はこんな感じです。なんかうちの環境では Test::Class のテストが通らないっていうあれな具合ですが、force install しちゃいました。

*1:prove は Test::Harness に付属してくるテスト実行用コマンド