Perlでテストを書くときに使われるTest::Moreは「Perlを学ぶとき、真っ先に覚えたいモジュール」の一つです。でも意外に使い慣れてなかったりします。
- Test::Moreを使ってテストを書くべき。
- Test::Moreはテストを書くときぐらいしか使わない。
- テストを書くためだけに、Test::Moreを覚えるのは億劫。
見事にテストを書くこと自体ネガティブになっちゃいますね。ネガティブ・サイクルが回っちゃいますね。このままだとTest::Moreを覚えずにprintf()
頼りのテストばかり書いたり、もしかしたらテストも書かないまま一生グルグル回っちゃいます。
まだあります。条件1のところで「でもTest::Moreを使ってテストを書くべきだよ!」と強く言われて一回は書いたとしましょう。その時は多少なりTest::Moreを覚えたんです。でも条件2:テストを書くときぐらいしか使わないから忘れちゃって、条件3':テストを書くためだけに、Test::Moreを思い出すの億劫になります。帰ってきたネガティブ・サイクルです。
Test::Moreを普段使いして、条件2を壊しちゃいましょう。「Perlを学ぶとき、真っ先に覚えたいモジュール」でも、簡単なお試しコードを書くときに使おうよと勧めています。ここでは追加で、コマンドラインから直接perlコードを動かしてみるワンライナー(One Liner)でも、Test::Moreを使うことをお勧めします。
ワンライナーでモジュールを使うまで
perlコマンド実行時に-e
スイッチをつけると、スクリプトファイルではなく、コマンドライン上で直接コードを書いて実行させることができます。ワンライナーと呼ばれます。例えば「substr('abcde', 2, 1)
を実行したら、2文字目と3文字目、どちらが返ってくるんだっけ?」と思ったら、次のようにして実行することができます。
c:\> perl -e "print substr('abcde', 2, 1);"
c
ワンライナーでモジュールを使うこともできます。例えば、Webコンテンツを取得するのに役立つLWP::UserAgentを使ってみましょう。コード内でuse
してもいいのですが、-M
スイッチを使うのが一般的です。
c:\> perl -MLWP::UserAgent -e "print LWP::UserAgent->new->get('http://example.com/');"
HTTP::Response=HASH(0x2418214)
おや、オブジェクトが返ってきたみたいですね。でもこの中身を見ないと、どんなコンテンツを取得できたのかとか、成功したのか失敗したのかもよくわかりません。そんな時はまずData::Dumperですね。ワンライナーでも使えます。LWP::UserAgentとの併用もできます。-M
スイッチを二つ並べるのです。
c:\> perl -MData::Dumper -MLWP::UserAgent -e "print Dumper( LWP::UserAgent->new->get('http://example.com/') );"
$VAR1 = bless( {
'_content' => 'Can\'t connect to example.com:80 (10060)
LWP::Protocol::http::Socket: connect: 10060 at C:/strawberry/perl/vendor/lib/LWP/Protocol/http.pm line 51.
',
'_rc' => 500,
(略)
}, 'HTTP::Response' );
接続に失敗した、といったデータが格納されているようです(例示専用のドメイン名の'example.com'にアクセスしようとすればそうなります)。どうでしょう。-M
スイッチと-e
スイッチを使うワンライナーには馴染めたでしょうか?
perl -M使用するモジュール名 -e "実行したいコード"
ワンライナーでTest::Moreを使う
Test::Moreも、他のモジュールと同じように使えます。
perl -MTest::More -e "実行したいコード"
Test::Moreのいろいろな命令を使ってみましょう。
print
代わりにdiag
を使う
Test::Moreにはprint
文のように(正確にはprint STDERR
のように)使えるdiagという命令があります。まずsubstr
の実行結果を確かめたワンライナーを思い出しましょう。
c:\> perl -e "print substr('abcde', 2, 1)"
c
print
の代わりにTest::Moreのdiag
を使うと、次のようになります。
c:\> perl -e -MTest::More "diag substr('abcde', 2, 1)"
# c
ほぼ同じように使えますね。diag
の出力は、行頭に「# 」が入っていて、スクリプト内でprintなどしているときには区別がつきやすくなります。もう一つ、出力対象が未定義値の時はundefが出力されます。これは「空文字列かundefか」が区別できて便利です。
c:\>perl -MTest::More -e "diag undef"
# undef
Dumper
代わりにexplain
を使う
Data::DumperのDumper
命令のように、リファレンスを文字列に変換してくれるexplain
という命令があります。Dumper
を使った例は以下でした。
c:\> perl -MData::Dumper -MLWP::UserAgent -e "print Dumper( LWP::UserAgent->new->get('http://example.com/') );"
$VAR1 = bless( {
'_content' => 'Can\'t connect to example.com:80 (10060)
LWP::Protocol::http::Socket: connect: 10060 at C:/strawberry/perl/vendor/lib/LWP/Protocol/http.pm line 51.
',
'_rc' => 500,
(略)
}, 'HTTP::Response' );
Test::Moreのexplain
を使うと、次のようになります。
C:\>perl -MTest::More -MLWP::UserAgent -e "diag explain( LWP::UserAgent->new->get('http://example.com/') );"
# bless( {
# '_content' => 'Can\'t connect to example.com:80 (10060)
#
# LWP::Protocol::http::Socket: connect: 10060 at C:/strawberry/perl/vendor/lib/LWP/Protocol/http.pm line 51.
# ',
# '_headers' => bless( {
# (略)
# }, 'HTTP::Response' )
ほぼ同様の出力が得られてますね。explain
では、キーがアルファベット順で出力されていたり、$VAR1 =
が省略されていたり、インデントが調整されていたりと少し見た目が整えられています。
is
で期待している値になるか確かめる
substr('abcde', 2, 1)
を実行した時に「2文字目が返って来るか」までワンライナーで確認させようと思ったら、次のようになるでしょう。
c:\> perl -e "$_ = substr('abcde', 2, 1); if ($_ eq 'b') { print 'ok' } else { print 'not ok' }"
not ok
Test::Moreであれば、is
という命令を使えます。
C:\> perl -MTest::More -e "is(substr('abcde', 2, 1) , 'b')"
not ok 1
# Failed test at -e line 1.
# got: 'c'
# expected: 'b'
# Tests were run but no plan was declared and done_testing() was not seen.
Test::Moreのis
を使うと、結果に加えて、not okの時は実際の値(got)と期待していた値(expected)も出力してくれます。結果がokの時にはこれらは出力されないのもよい感じです。is
の逆で、値が異なることを確かめるためのisnt
という命令もあります。
like
で正規表現にマッチするか確かめる
たとえば今日が週末かどうか、確かめてみましょう。sat|sun
という正規表現を使えばよさそうです。
C:\> perl -e "$_ = localtime; if ($_ =~ /sun|sat/i) { print 'ok' } else { print 'not ok' }"
not ok
Test::Moreには、この正規表現とマッチするかを調べるためのlike
という命令があります。
C:\> perl -MTest::More -e "$_ = localtime; like($_, qr/fri/i)"
not ok 1
# Failed test at -e line 1.
# 'Fri Mar 6 19:38:13 2015'
# doesn't match '(?^i:sat|sun)'
# Tests were run but no plan was declared and done_testing() was not seen.
週末ではなかったことと、localtime
の出力(は金曜日だったこと)が分かります。like
の逆で、値が正規表現にマッチしないことを確かめるためのunlike
という命令もあります。
is_deeply
で配列や連想配列などが一致するか確かめる
配列リファレンスや連想配列(ハッシュ)リファレンスの中を、再帰的に比較してくれるis_deeply
という命令もあります。例えば、まず次のようなワンライナーを考えましょう。第一引数を名、第二引数を姓として、連想配列リファレンスにしています。diag
はprint
のようなもの、explain
はData::DumperのDumper
のようなものでした。
C:\> perl -MTest::More -e "$_ = {'FirstName' => $ARGV[0], 'LastName' => $ARGV[1]}; diag explain($_)"
実際に引数をつけて実行すると、次のようになります。
C:\> perl -MTest::More -e "$_ = {'FirstName' => $ARGV[0], 'LastName' => $ARGV[1]}; diag explain($_)" Makio Tsukamoto
# {
# 'FirstName' => 'Makio',
# 'LastName' => 'Tsukamoto'
# }
例えば名(LastName)だけが期待する値と一致するか確認するのならば、is
で充分です。
C:\> perl -MTest::More -e "$_ = {'FirstName' => $ARGV[0], 'LastName' => $ARGV[1]}; is($_->{'FirstName'}, 'Makio')" Makio Tsukamoto
ok 1
全データを確認するとなると、is
がどんどん並んでいくことになります。そんな時はis_deeply
です。
C:\>perl -MTest::More -e "$_ = {'FirstName' => $ARGV[0], 'LastName' => $ARGV[1]}; is_deeply($_, {'FirstName' => 'Makio', 'LastName' => 'Tsukamoto'})" Makio Tsukamoto
ok 1
もちろん、not okの時は、詳細も出力されます。
C:\> perl -MTest::More -e "$_ = {'FirstName' => $ARGV[0], 'LastName' => $ARGV[1]}; is_deeply($_, {'FirstName' => 'Larry', 'LastName' => 'Wall'})" Makio Tsukamoto
not ok 1
# Failed test at -e line 1.
# Structures begin differing at:
# $got->{FirstName} = 'Makio'
# $expected->{FirstName} = 'Larry'
そのほかのTest::Moreの命令を調べる
もうTest::Moreをワンライナーで使うことには慣れたでしょうか?これらの他に役立つ命令があるか気になり始めたでしょうか?もしそうだったら、そろそろTest::Moreのドキュメントを眺めてもいいころです。少し旧い版が元ですが、perldoc.jpコミュニティによる和訳もあります。
まとめ
Test::Moreは簡単なお試しコードを書くときにはもちろん、コマンドラインからのワンライナーでも使えるモジュールです。explain
やis_deeply
などの命令は間違いなく便利ですし、他の命令も、使わない場合より便利かどうかはともかく、使うのが手間とか不便ということはありません。
Test::Moreを使い慣れて、その出力を見慣れておくと、テストを書くのはずっと気楽なことになるでしょう。テストを書くと、スクリプトやアプリケーションをメンテナンスし続け、更新していくことが少し気楽になります。
命令は、必要になったものから一つずつ覚えていけばよいと思います。ぜひテストのときだけではなく、Test::Moreを普段使いしてみてください。