Test::Moreをワンライナーで普段使い

Perlでテストを書くときに使われる[Test::More](http://search.cpan.org/dist/Test-Simple/lib/Test/More.pm)は「[Perlを学ぶとき、真っ先に覚えたいモジュール](http://bayashi.net/diary/2013/0415)」の一つです。でも意外に使い慣れてなかったりします。

1. Test::Moreを使ってテストを書くべき。
2. Test::Moreはテストを書くときぐらいしか使わない。
3. テストを書くためだけに、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](http://search.cpan.org/dist/Data-Dumper/Dumper.pm)ですね。ワンライナーでも使えます。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 - search.cpan.org](http://search.cpan.org/dist/Test-Simple/lib/Test/More.pm)
* [Test::More - テストを書くためのもう一つのフレームワーク - perldoc.jp](http://perldoc.jp/docs/modules/Test-Simple-0.99/lib/Test/More.pod)（perldoc.jpの和訳）

# まとめ

Test::Moreは簡単なお試しコードを書くときにはもちろん、コマンドラインからのワンライナーでも使えるモジュールです。`explain`や`is_deeply`などの命令は間違いなく便利ですし、他の命令も、使わない場合より便利かどうかはともかく、使うのが手間とか不便ということはありません。

Test::Moreを使い慣れて、その出力を見慣れておくと、テストを書くのはずっと気楽なことになるでしょう。テストを書くと、スクリプトやアプリケーションをメンテナンスし続け、更新していくことが少し気楽になります。

命令は、必要になったものから一つずつ覚えていけばよいと思います。ぜひテストのときだけではなく、Test::Moreを普段使いしてみてください。