prove (Test::Harness) コマンドの --state オプション

テストが膨大になっていくと,あるテストでは時間がかかったりして「そのテストはもう成功することがわかっとるっちゅうねん;失敗するテストだけ再テストしたいっちゅうねん」ってことになったりします。そんなときに使えるのが prove コマンドの --state オプションです。


--state オプションに failed という引数をわたすと,「テストの対象は前回 fail したテストスクリプトのみ対象」という意味になります。ステートを保存する save と併用して指定してみます。

% prove --state failed,save
No saved state, selection will be empty
Files=0, Tests=0,  0 wallclock secs ( 0.00 usr +  0.00 sys =  0.00 CPU)
Result: NOTESTS

「No saved state」と怒られてしまいました。なので初回のみステートを明示的に保存する必要があります。

% prove --state save
t/10_parse_args.....ok    
t/11_util_search....ok    
t/12_util_text......ok   
t/20_ctrl_words.....1/? 
#   Failed test at t/20_ctrl_words.t line 26.
# Looks like you failed 1 test of 5.
t/20_ctrl_words..... Dubious, test returned 1 (wstat 256, 0x100)
 Failed 1/5 subtests 

Test Summary Report
-------------------
t/20_ctrl_words (Wstat: 256 Tests: 5 Failed: 1)
  Failed test:  5
  Non-zero exit status: 1
Files=4, Tests=48,  2 wallclock secs ......

4つのテストのうち1つだけ失敗しました。

これでステートが保存されたので,もっかい --state failed,save を指定して実行してみます。

% prove --state failed,save
t/20_ctrl_words....1/? 
#   Failed test at t/20_ctrl_words.t line 26.
# Looks like you failed 1 test of 5.
t/20_ctrl_words.... Dubious, test returned 1 (wstat 256, 0x100)
 Failed 1/5 subtests 

Test Summary Report
-------------------
t/20_ctrl_words (Wstat: 256 Tests: 5 Failed: 1)
  Failed test:  5
  Non-zero exit status: 1
Files=1, Tests=5,  0 wallclock secs ......
Result: FAIL

さきほど失敗した t/20_ctrl_words というテストケースだけ実行されました。あとはこのテストケースが通るようモジュールを修正していくだけです。


Test::Harness 3.14 から,テストスクリプトの修正日時をみて,前回実行時以降に修正したものを対象にする「fresh」という引数が追加されました。

% touch t/12_util_text.t

% prove --state fresh,failed,save
t/12_util_text.....ok   
t/20_ctrl_words....1/? 
#   Failed test at t/20_ctrl_words.t line 26.

...... snip snip snip ......

Files=2, Tests=8,  1 wallclock secs ......
Result: FAIL

fresh,failed のように指定したので,更新されたテストスクリプトが先に実行され,次に前回 fail したスクリプトが実行されました。--state failed,fresh,save のような順序にすると,先に fail したスクリプトが実行されます。


--state に指定できるトークンには,いままでに指定したような「テスト対象を絞り込む」タイプのものと,new, old のように「テストを実行する順序を制御する」タイプのものがあります。このへんは prove コマンドのマニュアルApp::Prove::State の perldoc を読めばわかりますし,App::Prove::State のソース(具体的には sub apply_switch)を読めば((最初,複数オプションを指定すると AND 条件になってしまうのではないかと sub App::Prove::State::apply_switch をオーバーライドするツールとか書いてました。実際には後述のように OR 条件になってます。ま,そんな失敗談はさておき,ちょっと凝った条件でテスト対象を絞り込みたい場合は,apply_switch あたりを自分で書くといいかもしれません。))具体的に動作がわかります。


で,たとえば --state failed,all,save のようにすると,全テストスクリプトを対象とするんだけど fail したテストを最初に実行してね,という意味になります(ちとわかりにくいんですが,先頭のトークンの絞り込み結果から順番にテスト対象にマージされていくってことです)。


さきほどから「ステートを保存」と書いてきましたが,prove を実行したディレクトリに .prove という単一 YAML ファイルで保存されます。実行成否だけではなく,テストの実行にかかった時間なども記録されているので,このファイルを使えばテストケースの実行時間が遅いものを調べることもできます。ということを id:dann さんが Perlで遅いテストケースを調査する方法 - dann's blog - # で書いてました。


とるにたらない注意点ですが,fresh オプションは,あくまで「テストスクリプトの mtime」をみているだけです。なので過信して --state failed,fresh,save で,がしがしとモジュールを修正していって,通った通ったと喜んでいると,すでに pass したテストが動かなくなっていた!ということもあります(あたりまえ)。デバッグが一通りすんだら --state hot,all,save するなり --state つけずに動かすなり,.state ファイルを削除するなりするべきでしょう((自分ローカルで開発しているなら,Makefile の test ターゲットには --state fresh,failed,save したものを,fulltest ターゲットに --state hot,all,save を指定するというのもいいかもしれません。))。