perlのテストフレームワークである、TAP::Harnessの動きを調べました。わかりやすく言えばproveコマンドの中身です。
*.t(予備知識)
説明不要だと思いますが、モジュール開発者が書くテストスクリプトです。個々の*.tは単なるスクリプトで、実行されるとTAPを標準出力へ吐きます。こんなの。
1..2 ok 1 not ok 2
TAPを吐くのは、Test.pm とか Test::Moreとかが担当しているお仕事で、TAP::Harnessは何もしないです。
なお、書式さえ守ってればTest.pmやTest::Moreを使わなくても単にprint文で吐いてもOKだったりします。
App::Prove
proveコマンドの実装です。引数の解釈とかをして、TAP::Harnessを起動します。
TAP::Harness
テストを起動させるruntestsを持っています。runtestsに*.tのファイル名をどさっと渡すと、片っ端から起動してテスト結果を集めて表示してくれます。直列での実行だけでなく、並列実行とかもできます。
TAP::Harnessは、後述するParserとFormatter::Session(とFormatter)とAggregatorをコントロールしてテストを進めます。大まかな流れとしては、
- テストスクリプト1つに対して、1つのParserとFormatter::Sessionを作る
- ParserがTAPを解析してトークン(Parser::Result)を作る
- Formatter::Sessionがトークンを整形して途中経過の表示を行う
- Aggregatorに結果を登録する
- テストスクリプトの個数だけ、1〜4を繰り返す
- FormatterがAggregatorを解析し、サマリを表示する
となります。
なお、runtestsの他にaggregate_testsと言うメソッドも持っているのですが、複数種類のHarnessによるテスト結果を一つのAggregatorでサマリしたいとき以外は使う必要はないと思います。
Parser
テストを起動してTAPを吐かせて、それを解析してトークンに分けます。
二つのイテレータ
Parserでは「TAPの読み込み」と「トークンの切り出し」の双方をイテレータとして扱うので、混乱しないように注意が必要です。
前者のTAPの読み込みは TAP::Parser::Source の get_streamの戻り値である TAP::Parser::Iterator が実体となります。*1
後者のトークンに関しては、Parser自体がイテレータとなっていて $parser->next のように呼び出します。TAP::Parser::Resultオブジェクトが順番に返ってきます。
テストの起動
個々のテスト(*.t)は、Parserが作成されるときに、
- コンストラクタからTAP::Parser::Source::Perlのget_streamが呼ばれる*2
- TAP::Parser::IteratorFactoryのmake_iteratorが呼ばれる*3
- TAP::Parser::Iterator::Processのnew で起動
みたいな感じで起動されます。
TAPの解析
TAP::Parser::Grammar がstream(TAP::Parser::Iterator)からの入力を解析します。tokenize でTAPの各行を TAP::Parser::Resultオブジェクトに変換して返します。
テスト結果の集計
個々の*.tのテスト結果は、Parserが保持します。(トークンを戻す)イテレーションごとに、結果が蓄積されます。
パースが完了した後は、tests_runとかhas_problemsとかpassed、failed等のメソッドで結果を取り出せます。
FormatterとFormatter::Session
ParserとAggregatorからの出力を加工して表示します。デフォルトではConsole用とFile用の二つのFormatterが用意されてますが、どちらも文字列としての出力にしか対応してないので、Perlのデータ構造でテスト結果を得たい時はこれらを自作するとよさげです。
FormatterとFormatter::Session は組になっていて、以下のような仕事の分担をします。
- Formatter::Session->result → Parserからのトークンの詳細を表示(Parser::Result)。*4
- Formatter->summary → 全テストの実行結果をサマリ表示(Aggregator)。
なお、Formatter::Sessionのインスタンス化する義務はFormatter->open_testにあるみたいです。個々の*.tに対してopen_testが呼ばれるので、適切なFormatter::Sessionを作り、返す必要があります。Formatter->open_test に対する close_testはFormatter::Session側に実装します。
Aggregator
テスト結果(Parserオブジェクト)をHarnessから詰め込まれます。Parserがある程度サマってくれてるので、それを合計するのが主な仕事となります。
Harness->runtestsの戻り値でもありますが、この中にはテスト結果の詳細(Parser::Result)は入ってません。
まとめ
Parserがテストの起動と集計に片足(両足?)を突っ込んでいて、ここに責務が集中しています。
また、テスト結果はFormatterとFormatter::Sessionに流れ込んでそのまま標準出力行きとなってます。Perlのデータ構造でテスト結果を得たい時は、ここでキャッチするといいです。