tsucchi’s diary(元はてなダイアリー)

はてなダイアリー(d.hatena.ne.jp/tsucchi1022)から移行したものです

subtest と Hook::LexWrap を使って xUnit みたいな setUp, tearDown をする

発見したのは結構前なのですが、twitter でつぶやいても(これとかこれ)、あんまり(っていうか全く)反響なかったのでこっちにも書いておきます。

Perl でテストを書いているときに、xUnit の setUp/tearDown*1を使いたい場合、Test::Classを使ってテストを書いて、setup, teardown を使うのが一般的かな、と思います。こんな感じね。

#!/usr/bin/perl

use strict;
use warnings;

use Test::Class;

MyTest->runtests();

package MyTest;
use parent qw(Test::Class);
use Test::More;

sub set_up :Test(setup) {
    diag("setup");
}

sub tear_down :Test(teardown) {
    diag("teardown");
}

sub fixture_setup :Test(startup) {
    diag("startup");
}

sub fixture_teardown :Test(shutdown) {
    diag("shutdown");
}

sub my_test :Test(1) {
    ok(1); #テストのつもり
}

sub my_test2 :Test(1) {
    is("1", "1"); #これもテストのつもり
}

で実行すると、こんな感じ。

tsucchi@over[120]% perl a.pl
# startup
# setup
1..2
ok 1 - my test
# teardown
# setup
ok 2 - my test2
# teardown
# shutdown

で、最近発見した別の方法が、subtest と Hook::LexWrapを組み合わせる方法。Hook::LexWrap の wrap は任意のサブルーチンの前後に処理をフックできるので、subtest にフックさせちゃう。こんな感じ。

#!/usr/bin/perl
use strict;
use warnings;

use Test::More;
use Hook::LexWrap;

BEGIN { # startup は BEGIN で代用
    diag("startup");
}

END { #shutdown は END で代用
    diag("shutdown");
}

wrap 'subtest', 
    pre => sub { #setup の代わり
        diag("setup");
    },
    post => sub { #teardown の代わり
        diag("teardown");
    };

subtest 'my_test', sub {
    ok(1); #テストのつもり
};

subtest 'my_test2', sub {
    is("1", "1"); #これもテストのつもり
};

done_testing();

で、実行するとこんな感じ。

tsucchi@over[126]% perl b.pl
# startup
# setup
    ok 1
    1..1
ok 1 - my_test
# teardown
# setup
    ok 1
    1..1
ok 2 - my_test2
# teardown
1..2
# shutdown

ね、大体同じでしょ?Test::Class はインストール結構めんどいし、あんまり Perl っぽくないので、今回紹介した subtest + Hook::LexWrap でいいんじゃないかなー、と思っています。

ちなみに、テストクラスにフィールドを持たせたい場合は単純に、パッケージローカル変数とかにしちゃえばいいので、そういった管理も楽チンです。

困りそうなパターンは、テストクラスを継承したい場合とか、あと Hook::LexWrap が caller() をごにょごにょしてるので、同じようなことをしているサブルーチンをテストすると多分死ねます。それと、Hook::LexWrap::wrap() は、ブロックでくくると wrap する範囲を局所化できるので、hook したい内容を同一パッケージ内で差し替えたりとかも簡単に出来ちゃいますが、やりすぎると訳わかんなくなるはずなので、ご利用は計画的に、ってとこですかね。

*1:念のために書いとくけど、テスト前後に実行するメソッドの事ね。テストで使うインスタンスを準備したり、テスト後にリソースを開放したりします