# Perlでテストがこけて泣きそうなときのTIPS {{tag: perl, cpan, development}} Perl 書いてりゃ {{cpan: Test::More}} でテスト書きまくると思うのですが、Test::More っていうか、まあ別に Test::More だけがそうというわけでもないのですが、テストこけたときのケアが十分じゃないなと思うときがけっこうあります。 開発過程で書いてるコードというのは、いつもいつも確信を持って書いているわけではないわけで、それでなくてもうっかり間違うときもあり、せっかくテスト書いているのに何だかよくわからない理由でこけてパスできなくて時間を浪費してしまったりが日常になってたりしませんか? そういうのを繰り返しているとやがてテスト嫌いになりテスト書かなくなって本番コードにデバッグコードが入り乱れ、リファクタもどんどん不可能になって小回り効かないままプロジェクトが失敗して彼女に振られてしまうわけですね。困ります。 ## note diag explain Test::More には `note` と `diag` と `explain` というレア4くらいの便利関数があります。SYNOPSIS しか読んでない人は知らないかもしれません。 note はテスト結果にメッセージを出す関数で、verbose TAP じゃないときは出力されないという便利さがあります。なので気軽にテストの情報を出しておけます。 対して diag は、いつも出力されるので、以下のようにこけたときに出力を得たい場面で使うと便利です。 ok $false or diag $false; explain は、ダンパーです。リファレンスをダンプした結果を見せてくれます。 use Test::More; my $hash = { foo => 1}; note explain $hash; is_deeply($hash, {foo => 2}); done_testing; # { # 'foo' => 1 # } not ok 1 # Structures begin differing at: # $got->{foo} = '1' # $expected->{foo} = '2' 1..1 # Looks like you failed 1 test of 1. {{cpan: Data::Dumper}} をわざわざ使う必要ありませんね。 ## Wide character in print Test::More なテストで decoded な文字列をテストしてこけたり、use utf8 しててこけたりすると、うっとおしい場面というのがあります。 use Test::More is("\x{410}", "\x{420}"); done_testing; not ok 1 Wide character in print at /usr/lib/perl5/5.8.8/Test/Builder.pm line 1826. # got: 'А' # expected: 'Р' 「Wide character in print」という警告が出てしまったり、ターミナルで化けたりするのです。 これは {{cpan: Test::More::UTF8}} で回避できます。 use Test::More; use Test::More::UTF8; is("\x{410}", "\x{420}"); done_testing; not ok 1 # got: 'А' # expected: 'Р' 1..1 # Looks like you failed 1 test of 1. `use utf8` したテストには必須ですね。 ## unlike Test::More に `unlike` という、マッチしたらだめよ、っていうテストをする関数があります。 unlike $text, qr/expected/; $text が qr/expected/ にマッチしたらこける、ということなんですが、$text が長大でマッチの条件がそれなりに複雑だったりすると、こけたとき、どこでマッチしてしまったのか探しにくいときがあります。さすがに、普通のテキストならすぐ見つかるのだけど。 そんなときは、{{cpan: Test::More::Unlike}} を use しておくと、マッチしてしまった位置をこけたときに結果に足してくれます。 use Test::More tests => 1; use Test::More::Unlike; unlike 'abcdef', qr/cd/; not ok 1 # Failed test # 'abcdef' # matches '(?-xism:cd)' # matched at line: 1, offset: 3 # Looks like you failed 1 test of 1. 「matched at line: 1, offset: 3」便利ですね。 ## binary text 二つの文字列を is でテストして、以下のように fail してしまうことがあります。 not ok # got: 'abc' # expected: 'abc' 1..1 # Looks like you failed 1 test of 1. 同じ文字列のように見えるのに、なぜこけているのか謎です。 実は、以下のようなテストでした。 use Test::More; my $str = "abc\0"; is $str, "abc"; done_testing; not ok 1 # got: 'abc' # expected: 'abc' 1..1 # Looks like you failed 1 test of 1. レポートで `\0` が抜け落ちてしまっているわけですね。上の例だと一目瞭然ですが、あれこれした結果で紛れ込んでいると、少々探しにくいかもしれません。 こういった場合には、{{cpan: Devel::Peek}} です。化けた文字列を見たりするのによく使われたりしますが、今回のような not printable な文字を見るのにも使えます。 use Test::More; use Devel::Peek; my $str = "abc\0"; is $str, "abc" or Dump $str; done_testing; not ok 1 # got: 'abc' # expected: 'abc' SV = PV(0x80f0b00) at 0x80f06f0 REFCNT = 1 FLAGS = (PADBUSY,PADMY,POK,pPOK) PV = 0x8105d88 "abc\0"\0 CUR = 4 LEN = 8 1..1 # Looks like you failed 1 test of 1. この変数どうなってんだ、というのはだいたい Devel::Peek で解決できます。 ### Test::HexString 最初からバイナリ列を扱っているという認識があるなら、{{cpan: Test::HexString}} が便利です。 use Test::More; use Test::HexString; my $str = "abc\0"; is_hexstr $str => "abc"; done_testing; not ok 1 # at bytes 0-0xf (0-15) # got: | 61 62 63 00 .. .. .. .. .. .. .. .. .. .. .. .. |abc. | # exp: | 61 62 63 .. .. .. .. .. .. .. .. .. .. .. .. .. |abc | 1..1 # Looks like you failed 1 test of 1. `is_hexstr` で、こけたら hex dump を見せてくれます。バイナリアンの心をくすぐりますね。 ## まとめ こけてからがテスト。