Perlでのテストメモ書き

なんとなくPerlの書きっぷりがわかってきたので、PerlでのUnitTestなど調べた。そのメモ。

基本的に Test::More ってのが Perl5.8 からついてくるので、これを使えばよさそう。RSpec 脳の人は Test::Spec とか使えばいいんじゃないかな?

Perlのプロジェクトの雛形を作らねばらんが、どんなディレクトリ構成が標準的なのかよくわからん。例によってググってみると、雛形を作成してくれるモジュールがいろいろあるらしい、最近できたっぽい、Module::Setup を使うことにした。使い方はここに書くより、適当に再度ググったらよかろう。

実際に書いてみるのが学習の早道なので、例によってTDD Boot Camp(TDDBC) - TDDBC大阪2.0/課題を借りる。

$ module-setup VendingMachine

と適当に作成。いろいろディレクトリが作成されるけど、そのうち勉強するということにして、lib と t の中身だけ気にすればいいでしょう。

とりあえず、テストケースの数をいちいち先頭に書くのは面倒なので t/00_compile.t を

use Test::More;

BEGIN { use_ok 'VendingMachine' }

done_testing;

な感じに修正する。とりあえず実行。

$ prove -l t

t/00_compile.t .. ok   
All tests successful.
Files=1, Tests=1,  0 wallclock secs ( 0.01 usr +  0.00 sys =  0.01 CPU)
Result: PASS

できた。これで終わってもいいけど、もう少し書こう。
Test::More は subtest によってテストを階層化できるみたいなので、どんどん利用する。フラットなテストケースって見難いよね。(似非)テストファーストで、貨幣を投入できる程度のテストをまず書く。

use strict;
use warnings;
use Test::More;

use VendingMachine;

subtest "貨幣が投入できる" => sub {
    subtest "初期状態(貨幣の投入なし)" => sub {
        my $vm = VendingMachine->new;
        is($vm->total, 0, "合計額はゼロ円");
        $vm->back;
        is($vm->change, 0, "戻ってくるお釣りもゼロ円");
    };
};

done_testing;

とりあえず、VendingMachine には new と total と back と change くらいが必要だよ、というテスト。
それっぽく、VendingMachine を実装。クラスの書き方とか適当にググっただけなので、おかしな書き方になってるかもしれんけど気にしない。

package VendingMachine;
use strict;
use warnings;
our $VERSION = '0.01';

sub new {
    my $class = shift;

    my $self = {
        total => 0,
        change => 0,
    };

    return bless $self, $class;
}

sub total {
    my $self = shift;
    return $self->{total};
}

sub change {
    my $self = shift;
    return $self->{change};
}

sub back {
    my $self = shift;
    $self->{change} = $self->total;
    $self->{total} = 0;
}

1;

実行。

$ prove -l t
t/00_compile.t .. ok   
t/01_money.t .... ok   
All tests successful.
Files=2, Tests=2,  0 wallclock secs ( 0.03 usr  0.00 sys +  0.01 cusr  0.00 csys =  0.04 CPU)
Result: PASS

通ったので、テストを追加する。

use strict;
use warnings;
use Test::More;

use VendingMachine;

subtest "貨幣が投入できる" => sub {
    subtest "初期状態(貨幣の投入なし)" => sub {
        my $vm = VendingMachine->new;
        is($vm->total, 0, "合計額はゼロ円");
        $vm->back;
        is($vm->change, 0, "戻ってくるお釣りもゼロ円");
    };
    subtest "貨幣を投入する" => sub {
        my $vm = VendingMachine->new;
        $vm->put_in(100); $vm->put_in(50);
        is($vm->total, 150, "合計額が得られる");
        $vm->back;
        is($vm->change, 150, "払い戻すと合計額が戻る");
        is($vm->total, 0, "払い戻すと合計額がゼロになる");
    };
};

done_testing;

貨幣を投入する put_in が必要になるので実装するよ。

...
sub put_in {
    my ($self, $money) = @_;
    $self->{total} += $money;
}

で、以下貨幣として許されない額が put_in に与えられた場合、などと続く。
冗長にテストを実行するとこんな感じ

$ prove -lv t/01_money.t
t/01_money.t .. 
        ok 1 - 合計額はゼロ円
        ok 2 - 戻ってくるお釣りもゼロ円
        1..2
    ok 1 - 初期状態(貨幣の投入なし)
        ok 1 - 合計額が得られる
        ok 2 - 払い戻すと合計額が戻る
        ok 3 - 払い戻すと合計額がゼロになる
        1..3
    ok 2 - 貨幣を投入する
    1..2
ok 1 - 貨幣が投入できる
1..1
ok
All tests successful.
Files=1, Tests=1,  0 wallclock secs ( 0.02 usr  0.00 sys +  0.01 cusr  0.00 csys =  0.03 CPU)
Result: PASS

表示が気にいらない人(つまり俺)は Test::Flatten とか使うといいよ。

$ prove -lv t/01_moeny.t
t/01_money.t .. 
# ------------------------------------------------------------------------------
# 貨幣が投入できる
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# 初期状態(貨幣の投入なし)
# ------------------------------------------------------------------------------
ok 1 - 合計額はゼロ円
ok 2 - 戻ってくるお釣りもゼロ円
# ------------------------------------------------------------------------------
# 貨幣を投入する
# ------------------------------------------------------------------------------
ok 3 - 合計額が得られる
ok 4 - 払い戻すと合計額が戻る
ok 5 - 払い戻すと合計額がゼロになる
1..5
ok
All tests successful.
Files=1, Tests=5,  0 wallclock secs ( 0.01 usr  0.01 sys +  0.01 cusr  0.00 csys =  0.03 CPU)
Result: PASS
まとめ
  • Test::More でテスト書けるよ。
  • Test::Flatten ナイス!
  • before / after フックはない(多分)