まずは以下のソースを見て欲しい。

#!/usr/bin/env perl
use strict;
use warnings;
 
sub hoge {
    die "hoge";
}
 
hoge({ return'hogehoge' });

これを実行するとどうなるかというと、

tsucchi@dev[1017]$ perl test.pl
Can't return outside a subroutine at test.pl line 9.

こんな感じでエラーになる。hoge の前に print などを入れると表示されることなどから、これはコンパイルエラーではなく、実行時エラーだと分かる。

では、この hoge()を呼んでいる部分を eval で囲むとどうなるだろうか?

ソース的には下記のような感じである。

#!/usr/bin/env perl
use strict;
use warnings;
 
sub hoge {
    die "hoge";
}
 
eval {
    hoge({ return'hogehoge' });
};
warn $@;
tsucchi@dev[1017]$ perl test.pl
Warning: something's wrong at test.pl line 12.

えーっと、die しない(12行目で「hoge」が出ない」ので、サブルーチンの実行自体がされていません。 その上 $@ にも入らなくてだいぶ残念な感じです。hogeの後ろに print とか入れると実行されないので、 hoge のところで eval から return してるっぽい、ということが分かります。

って、なんでですかー(AA略

まー何が悪いって、そもそも意図していたコードは

hoge(sub { return'hogehoge' });

と、coderef なのに sub を書き忘れた、って事なんですけどね。Try::Tiny とかの中でうっかり return を書くとやばいアレと現象的には 同じものなのかな。

これの何がやばいって、Test::Class を使ったテストの中で、Test::Mock::Guard の劣化コピーみたいなのを使ってテスト書いているので、

sub test_hoge : Tests {
    my ($self) = @_;
	my $guard = stub_class('Hoge', { do_something => sub { return 'aaa' } });
	...
}

みたいなのを山ほど書いてるんですが、うっかり typo というか sub を書き忘れて、

my $guard = stub_class('Hoge', { do_something => { return 'aaa' } });
}

とか書いてしまう事がある。んで、冒頭の現象が発生してしまうと、$@ が空なので、テストが実行されずに意図せず成功してしまう。 (return のところで return しちゃうので、後続の処理も行われない)。というとても悲しい感じになってしまうのです。

対策としては、

my $guard = stub_class('Hoge', { do_something => sub { 'aaa' } });

こんな感じで、

  • sub をつける
  • return 書くのをやめる
  • Tests ではなく、Test(2) のようにテスト数をちゃんと書く

くらいしか思いつかなくて、わりと悲しい感じです。

2014-05-16 追記

id: hide_o_55 さんのブクマコメで指摘ありました(ご指摘どうもです)が、Tests の代わりにテスト数を書くってのもアリですね。めんどくさいから普段はやってないのですが。。。

perl 108 idiom 4

© 2024 tsucchi with help from Jekyll Bootstrap and Bootstrap