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 したい内容を同一パッケージ内で差し替えたりとかも簡単に出来ちゃいますが、やりすぎると訳わかんなくなるはずなので、ご利用は計画的に、ってとこですかね。