本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーは、Perl関連のイベント
「吉祥寺.pm」の主催などで知られるMagnolia.Kさんで、テーマは「テストモジュールの使い方・作り方」です。
本稿のサンプルコードは、WEB+DB PRESS Vol.90のサポートサイトから入手できます。
Perlにおけるテスト
読者のみなさんはテストを書いていますか。
Perlは昔からテストをとても大事にしてきた言語です。テストを書くために必要なテストモジュールはコアモジュールとしてPerl本体と一緒にインストールされていて、すぐにテストが書ける環境が用意されています。CPANにアップされているモジュールには、たいてい十分な量のテストが用意されていて、安心して使うことができます(動くかどうかもわからないモジュールは使えないですよね)。
今回はテストモジュールの使い方や、テストモジュールの作り方を通じて、Perlにおけるテストの方法を解説します。また、2015年のクリスマスに正式版がリリースされたPerl 6でのテストの方法についても解説します。
なお、以降のコードは、特に注釈がない限りPerl 5.14.0
以降で動作するように書かれており、Perl 5.22.0
で動作確認を行いました。
また、紙幅の都合でuse strict
と、use warnings
はすべて省略していますが、実際にコードを試すときには必ず付けてください。
テストモジュールの使い方
まずはテストモジュールの使い方とテストの実行方法を解説します。
Test::More──Perlのテストの基本
テストモジュールの中で最も基本的な機能を提供するモジュールがTest::More
です。Test::More
はコアモジュールとしてPerl本体と一緒にインストールされています。
Test::Moreを使ったテスト
最初のテストとして、Perlの組込み関数であるlength関数[1]の挙動を確認するlength_test.t[2]というテストを書いてみます(リスト1)。
1行目は、テストモジュールのTest::More
を使用するための宣言です。以降で使用するis
という関数が使えるようになります。
2行目は、Test::More
のis
関数を使ってテストを実行しています。is
関数は引数を3つ取り、1つ目はテストの対象の値(主に関数やメソッドの戻り値)、2つ目は期待する値、3つ目はテストの名前を指定します。ここではlength("perl")
の戻り値と、期待する値(4)を比較し、テストの名前としてlength test
を指定しています。
3行目では再びis
関数を使って今度はlength(undef)
と、0
を比較しています。なお、length(undef)
は実際にはundef
を返すので、このテストは失敗します(詳細はのちほど説明します)。
最後の4行目では、テストがすべて終わったことを宣言するため、Test::More
のdone_testing
関数を呼び出しています。
テストの実行
リスト1を通常のPerlのプログラムと同じように実行してください。
テストの結果が出力されました。1行ずつ見ていきましょう。
(1)でok
と出力されていますが、これは1つ目のテストが成功したことを示しています。後ろに続く数字(1
)と文字列は、そのテストが実行された順序を示す連番と、is
に渡したテストの名前です。
(2)でnot ok
と出力されていますが、これは2つ目のテストが失敗したことを示しています。その後ろはテストが成功したときと同様にテストの連番とテストの名前が表示されています。
(3)では、テストが失敗した原因の詳細が出力されています。最初にテストが失敗した箇所のファイル名と行番号(length_test.t line 3
)が出力され、続けて実際に得られた値(got
)と期待した値(expected
)が出力されています。この内容をもとにテストが失敗した原因を解析します。
すべてのテストが終わりdone_testing
が実行されると、(4)でそれまで実行したテストの数が1..[テスト数]
という形式で出力されます。
失敗したテストがあるとテストプログラムが終了するタイミングで、(5)のように失敗したテストの数が出力されます。
このようにPerlでは、テスト対象と期待する値を比較し、期待どおりならばテストが成功し、期待した内容と異なればテストが失敗する、というスタイルでテストを書いていきます。先ほどのis
関数は最も基本的なテストのための関数(テスト関数)ですが、このほかにも目的に合わせてさまざまなテスト関数が用意されています。以降ではよく使うテスト関数と、Test::More
が提供するそのほかの機能を解説します。
like──正規表現によるテスト
like
は正規表現を使うテスト関数です。引数の2番目に正規表現へのリファレンスを渡す点がis
と異なります。
cmp_ok──任意の演算子を使った比較テスト
cmp_ok
は指定した演算子で比較を行うテスト関数です。数値の大小比較など、任意の演算子を使って比較したい場合に使います。
is_deeply──リファレンスのテスト
is_deeply
はリファレンス用のテスト関数です。2つのリファレンスをたどりながら比較を行います。
リファレンスの先に一致しない箇所があるとテストが失敗し、その不一致箇所が出力されます。
plan──テストの数を指定する
リスト1で用いたdone_tesing
はすべてのテストを実行したあとにテストの数をカウントする関数ですが、plan
という関数で最初にテストの数を指定することもできます。
plan
を使わずに、Test::More
を読み込むときに指定することもできます。
テストの数と実際に実行したテストの数が不一致の場合、その旨のメッセージが出力されます。
以前はテストの数をあらかじめ指定する方法が主流でしたが、テストを追加したり、削除したりするたびにテストの数を修正するのはミスを誘発しやすいので、現在では最後にdone_testing
を呼び出す方法がお勧めです。
subtest──テストをまとめる
テストをたくさん書いていくと、1つのテストファイルの中のテストの量が多くなりすぎてだんだんと構造が見えにくくなっていきます。理解しやすい単位でテストをグルーピングするためにsubtest
というしくみが用意されています。
subtest
は、テストの名前とテスト関数を実行するコードリファレンスを受け取ります。
実行すると、まずコードリファレンスの中身が実行され、テストの結果がTAP形式で出力されます(TAP形式については後述します)。
先にコードリファレンスの中のテストが実行され、そのテストが全部成功したら(1)のようにsubtest
全体が成功となり、1つでもテストが失敗していると(2)のようにsubtest
全体が失敗となります。
このようにsubtest
はテストのコードを理解しやすいように階層化するために使いますが、使いすぎると今度はテスト結果の出力が読みにくくなってしまうので、あまりに階層が深く、複雑になるようなテストであれば、そもそも複数のファイルにテストを分けたほうがよいでしょう。
diag──解析用メッセージを出力する
テスト関数が失敗すると原因が出力されますが、独自の解析用の情報を付加したいときがあります(たとえば接続先のデータベースのインスタンス名を出力するなど)。そのようなときにはdiag
関数を使用します。
テストに失敗するとテスト関数自体の戻り値は偽になるので、上記の例ではテストが失敗したときのみDB
とUSER
の情報が出力されます。
diag
関数の出力はすべて#
が先頭に付加され、標準エラー出力にコメントとして出力されます。
BAIL_OUT──テストを中断する
BAIL_OUT
は、必要なデータベースサーバが立ち上がっていないなど、必要な条件がそろっていないときにテストを中断するために使用します。
BAIL_OUT
が呼び出されると、ただちに次のように出力され、テストは中断されます。BAIL_OUT
以降のコードはすべて無視されます。
テスト結果の出力はTAP形式
リスト1で、テストが成功したらok
、失敗したらnot ok
が出力されると説明しましたが、これはTAP(Test Anything Protocol)という規約に基づいています。規約といっても実にシンプルな内容で、次のルールを覚えれば十分です。
- テストが成功すれば
ok
、失敗すればnot ok
を出力する
ok
とnot ok
に続けて、テストの連番とテスト名を出力してもよい(出力しなくてもよい)
- テストの前か一番最後で、
1..[連番]
という形式でテストの数を出力する
- テストが継続できないときは
Bail out!
と出力し、ただちに実行を中断する
#
で始まる行は、エラー内容の詳細などテスト結果を補足する情報を出力する
- 上記以外の出力内容は、TAPとしてはすべて無効となる(テストの結果には影響しない)
Perlでは、テスト結果をすべてTAPという規約に従って出力することにより、次に解説するprove
コマンドでテスト結果を収集、解析できるようになっています。
なお、TAPに出力される内容に漢字などのUnicode文字列が含まれていると、有名なWide character in print...
という警告メッセージが出力されます。これを防ぐためには次のように、Test::More
を読み込む前に明示的に標準出力や標準エラー出力のエンコーディングを指定します。
テストを実行し結果を収集するproveコマンド
テストを一つ一つ実行し、TAPの出力を確認し……とやっていると非常に効率が悪いので、Perlにはまとめて複数のテストを実行し、TAP形式のテスト結果をサマライズしてくれるprove
というコマンドが用意されています。
リスト1をprove
コマンドで実行してみます。
1つでもテストが失敗すると、TAPの出力から失敗した箇所だけが抜き出されて出力され、どこでテストが失敗したのかがすぐにわかるようになっています。最後にはResult: FAIL
と表示されるので、最後の1行だけでもテストが失敗したことがわかります。
次に、リスト1の3行目をis(length(undef), undef,"undef test");
と書き換え、テストを成功させてみます。
TAPの詳細な出力はすべて省略され、とにかくテストがすべて成功したことだけが簡潔に表示されます。
なお、ここではテストのファイル名を指定しましたが、複数のテストをまとめて実行するときはt
というディレクトリにテストを置いておけば、prove
のみでまとめて実行してくれます。
次のように明示的にディレクトリの指定もできます。
このようにprove
コマンドを使うことで、効率的にテストの実行と結果の確認ができます。
そのほかのテストモジュール
Test::More
以外にも、CPANには、例外をテストするTest::Exception
や、標準出力の結果をテストするTest::Output
など、さまざまなテストモジュールがアップされています。
テストモジュールを探したいときは、CPANで「Test::」をキーワードに検索してみてください。検索結果が多すぎて、どこから手をつけたらよいかわからないときは、awesome-perl
やTask::Kensho::Testing
などを参照すると、よく使われている定番のテストモジュールが見つかるでしょう。
ここまでのまとめ
(1)ではテストモジュールの使い方について解説しました。Perlでは、Test::More
、TAPの読み方、prove
の3つを覚えればテストを始めることができますので、テストを書くまでの敷居がとても低くなっています。この3つを覚えたら、あとは実際のテストの例をCPANにアップされている数々のモジュールのtディレクトリを見て学んでいくとよいでしょう。
<続きの(2)はこちら。>