モダンPerlの世界へようこそ

第28回Test::More:no_planからdone_testingへ

計画的に実行するのはよいことですが

前回も紹介したように、Test Anything Protocolでは「これから10個のテストを実行します」と宣言する場合はこのように書くことになっていました。

use strict;
print "1..10\n";  # 宣言部
for (1..10) {
    print "ok $_\n";
}

このような宣言部の存在は、テスト結果をパースして分析するTest::Harnessのようなツールにとっては非常に便利なものですが、たとえば環境によってテストの数がかわるとき、あるいはテストファイルが非常に長くなってきたとき、はたまた多くの人が平行してファイルやテストの追加作業をしているため最後にマージするまでテストの数がわからないとき、事前にテストの数を把握していなければならないというのは、大きな制約にもなりえます。

単純そうに見えるTest Anything Protocolも、この20年の間に10回以上の改訂を経て、少しずつ柔軟性を増してきました。今回はその流れを簡単に追いかけてみることにしましょう。

skip_all

Perl 1.0の時代から使い続けられてきたTAPに最初の拡張が行われたのはPerl 5.2のベータリリースが行われていた1996年のことでした。その最初の拡張では個々のテストの終了ステータスをチェックするようになり、#で始まる行が出力された場合はコメントとして無視するようになったのですが、この年にはもうひとつ、テストの数を0と宣言することでテスト全体をスキップするという機能が追加されました。

use strict;
print "1..0\n";
exit;

これはいまでも特定の機能、特定の外部アプリケーションがないのでテストが実行できないとか、単に時間がかかるテストなのでふだんは省略したいという場合によく使われているのですが、planに直接0を指定するのは20世紀のやり方。

use strict;
use Test;
if ($^O eq 'MSWin32') {
    plan tests => 0;
    exit;
}
plan tests => 1;
ok 'tests for unix';

いまどきのTest::Moreでは意図しないところでテストがスキップされてしまうのを防ぐためtestsに0を指定することはできなくなっているので、plan skip_allを使います。

use strict;
use warnings;
use Test::More;

eval 'require Some::Module';
plan skip_all => 'this test requires Some::Module' if $@;

unless ($ENV{RELEASE_TESTING}) {
    plan skip_all => 'set RELEASE_TESTING to test';
}

skip

ただし、このようにテストファイル全体をスキップしてしまうのはかならずしも最善の方法とは限りません。大きなテストのごく一部のテストのみスキップしたい場合は、ファイルを分けるより、その部分のみスキップできるようにしたほうが効果的です。そのため、翌1997年にはこのように個々のテスト単位でスキップできるような仕掛けが用意されました。

use strict;
use Test;
plan tests => 2;
skip('this test fails', 1 == 0);
ok 'portable test';

いまどきの書き方ならこうなりますね。

use strict;
use warnings;
use Test::More tests => 2;
SKIP: {
    skip 'this test fails', 1;
    ok 1 == 0;
}
pass 'portable test';

スキップするといっても、単にテストを飛ばすだけでは最初に宣言したテストの件数と実行件数が一致せず、テストに異常があったとみなされてしまうので、実際にはテストを実行するかわりにスキップするテストの分だけokを出力して数あわせをしています。proveやmake testを通さずに直接perl skip.tしてみると、様子を確認できます。

> perl skip.t
1..2
ok 1 # skip this test fails
ok 2 - portable test

todo

skipは上手に使えば便利な仕組みですが、テストが失敗するからといってなんでもかんでもスキップしてしまうと、その後なんらかの事情でテストが失敗しなくなったときに(テストを実行していないので)気づけない、という問題が起こります。そのため、失敗は失敗として受け入れたうえでテスト全体の結果には影響を与えないようにしようというのが1998年に導入されたtodoの仕組みです。

当初のtodoは、planを指定するときに失敗してもかまわないテスト番号をあわせて指定するようになっていました。

use strict;
use Test;
plan tests => 2, todo => [1];
ok 0;
ok 1;

このテストを実行すると、ひとつめのテストは失敗するものの、それは既知のTODOであるとして無視され、全体としてはテストに通っていると判断されます。

> prove todo.t
todo.t .. # Failed test 1 in todo.t at line 4 *TODO*
#  todo.t line 4 is: ok 0;
todo.t .. ok
All tests successful.

Test Summary Report
-------------------
todo.t (Wstat: 0 Tests: 2 Failed: 0)
  TODO passed:   2
Files=1, Tests=2,  0 wallclock secs ( 0.03 usr +  0.03 sys =  0.06 CPU)
Result: PASS

ただし、この書き方はいかにも面倒ですし、大きなテストや複雑なテストでは番号を把握するのも一苦労です。そのため、Test::Moreが使われるようになるとこの方式は廃れ、紆余曲折を経て、いまのようなブロックを利用した書き方にとってかわられます。

use strict;
use warnings;
use Test::More tests => 2;

TODO: {
    local $TODO = 'not implemented';
    fail 'todo';
}
pass 'ok';

こちらの書き方の場合、影響範囲をブロックでくくるところまではskipと同じですが、スキップするテストの件数を指定しなくてもよいのが大きなポイント。いちいちテストを修正しなくても、モジュール(や、その他の環境)を直していけば順次テストが通るようになるので、テスト駆動開発もしやすくなりました[1]⁠。

no_plan

このTODOの例のように、21世紀のPerlテスト界ではなるべくテストの件数を数えなくてもすむようにという工夫がいくつも見られるようになるのですが、プロトコルそのもののほうでもその流れを助けるように、2001年4月にリリースされたPerl 5.7.1(に同梱されているTest::Harness 1.1702)からはテストの宣言部をテストの最後尾に書けるようになっています。

たとえば特定の環境ではスキップしたいテストがあったとしましょう。伝統的な書き方では最初にテストの件数を宣言しなければなりませんから、ひとまずスキップしなかった場合の数を宣言しておいて、必要に応じて空のokをはさむのが常套句でした。

use strict;
print "1..2\n";  # 宣言部
if ($^O ne 'MSWin32') {
    print "ok 1\n";
}
else {
    print "ok 1 # Skip\n";
}
print "ok 2\n";

このくらいであればスキップの機構を使わず、テストの件数を宣言するときに場合分けするやり方も考えられますが、この方式は宣言の前後に同じ条件分岐が出てきますからいかにも冗長です。

use strict;
my $total = $^O ne 'MSWin32' ? 2 : 1;
print "1..$total\n";  # 宣言部

my $count = 0;
if ($^O ne 'MSWin32') {
    printf "ok %d\n", ++$count;
}
printf "ok %d\n", ++$count;

これを21世紀風のTest Anything Protocolで書き直すと、たとえばこのように書くことができます。

use strict;
my $count = 0;
if ($^O ne 'MSWin32') {
    printf "ok %d\n", ++$count;
}
printf "ok %d\n", ++$count;
print "1..$count\n";  # 宣言部

ここではいちいち自分でカウンタの値を増やしていますが、Test::Moreなどのツールにはもともと(宣言したテストがすべて実行されたか検証するために)実行したテストの数を数える機能がついているので、それを使えばいちいちテストの数を数える必要もありません。実際に実行したテストの数を最後に宣言してやれば万事解決――となるはずでした。

no_planの問題点

ところが、Test::Moreの場合、つい最近まで、この宣言部の後置を利用するには最初に「no_plan」という特殊な宣言をしなければなりませんでした。先ほどの例をno_planを使って書き直すと、このようになります。

use strict;
use Test::More 'no_plan'; # Test::Moreの宣言部はここ
if ($^O ne 'MSWin32') {
    pass;
}
pass;
# Test Anything Protocolの宣言部はここに出力

このTest::Moreのテスト計画の宣言部と実際にTest Anything Protocolの宣言部が出力される場所のズレは、プロトコルを理解している人にとっても直感的ではなかったのですが、それ以上に、そのようなズレを実現するための仕掛けがいくつかの新しい問題を産むことになりました。

たとえば、テストが何らかの事情で途中で止まってしまったとき。本来であれば後続のテストを実行できなかったのですからテストは失敗と判断されるべきですが、no_planの場合は実際に実行したテストの数を宣言するという性質上、あたかもそれ以降のテストは最初から存在しなかったかのように、その時点までに実行したテストの数を宣言してしまいます(そのため、そこまでのテストに失敗がなければテストが通ったと判断されてしまいます⁠⁠。

あるいは、テストが途中でforkする場合。いまではTest::SharedForkというtokuhiromこと松野徳大氏がつくった便利なモジュールがあるのでそれほど問題にはならないのですが、このモジュールを使わない場合、たとえばこのようなテストは正しく動作しません。

use strict;
use Test::More 'no_plan';
if (my $pid = fork) {
    wait;
    pass "parent";
}
else {
    pass "child";
}

これは、実際にはno_planの問題ではなく、裏で使われているTest::Builder内部のカウンタが正しく増えないのが問題なのですが、たとえば自分でテスト計画を宣言すればこのような方法で回避することができます。no_planでは手の打ちようがありません。

use strict;
use Test::More tests => 2;

Test::More->builder->use_numbers(0); # テストが順不同でも許す
Test::More->builder->no_ending(1);   # child側の整合性チェックを省略する

if (my $pid = fork) {
    wait;
    pass "parent";
}
else {
    pass "child";
}

done_testing

このような問題点を改善するため、2009年3月にリリースされたTest::More 0.87_01からはdone_testingという新しいテストコマンドが追加されました。これは要するにTest Anything Protocolの宣言部を自分の指定した場所に出力するためのコマンドで、デフォルトでは実際に実行したテストの数を宣言しますが、必要に応じて自分で計算したテストの数を出力することもできるので、no_planでは対応できなかったforkがらみのテストなどでも正しく対応できるようになっています。

use strict;
use Test::More;

Test::More->builder->use_numbers(0); # テストが順不同でも許す
Test::More->builder->no_ending(1);   # child側の整合性チェックを省略する

if (my $pid = fork) {
    wait;
    pass "parent";
    done_testing(2);  # 明示的にテストの件数を指定する
}
else {
    pass "child";
}

ここではdone_testingを親のブロックのなかに収めていますが、子のブロックを抜けるときに明示的にexitすればdone_testingを最後に持っていくこともできます。

use strict;
use Test::More;

Test::More->builder->use_numbers(0); # テストが順不同でも許す
Test::More->builder->no_ending(1);   # child側の整合性チェックを省略する

if (my $pid = fork) {
    wait;
    pass "parent";
}
else {
    pass "child";
    exit;
}
done_testing(2);  # 明示的にテストの件数を指定する

もちろんno_planで対応できるようなテストであれば、done_testingの引数は省略できます。

use strict;
use Test::More;
if ($^O ne 'MSWin32') {
    pass;
}
pass;
done_testing;

また、done_testingはno_planと違ってテスト終了時に自動的に宣言部を追加する、という仕組みをとっていないため、done_testingにたどり着く前にテストが終わってしまった場合は「宣言部がない」というエラーを出すことも可能です。

たしかにdone_testingは便利ですが……

このように、done_testingは、正しく使えば従来のno_planが抱えていた問題をほとんどすべて解決できるようになったのですが、done_testingにも問題がないわけではありません。

たとえば、Test::NoWarningsというテストモジュールの例を見てみましょう。これはテストの実行中に警告が出なかったかどうかをENDブロックのなかで判定するのですが、この場合、単にdone_testingするだけでなく、テスト計画の数まで考慮してあげても、現時点では宣言部がテストのなかに埋め込まれてしまうためにテストに失敗します(done_testingの部分をENDブロックのなかに含めてもかわりません。なお、no_planはこのようなテストにも対応しています⁠⁠。

use strict;
use Test::More;
use Test::NoWarnings;

pass "without warning";

done_testing(2); # NoWarningsの分をひとつ追加

また、no_planと違って途中で(異常)終了した場合を検出できるようになったとはいえ、done_testingも、引数なしで使った場合は、期待したテストをすべて実行したかどうかの確認はできません。たとえば、このようにPODが埋め込まれたテストがあったとしましょう。

use strict;
use Test::More;

pass;

=pod

pass;

=pod

=cut

pass;

done_testing;

ご覧の通り、実はこのテストは=podディレクティブのくくり方が間違っていて、ふたつめの「pass;」がPODのなかに入り込んでしまっているのですが、ここではdone_testingを引数なしで使っているので、テストはもともとふたつしかなかったと「正しく」解釈して、正常終了します。もちろんそれは意図通りの結果ではありません。同様のことは、たとえば(意図とは違って)常に偽になってしまう条件式が含まれていたり、ブロックのくくり方が間違っていた場合などにも容易に起こることです。

だから、たしかにdone_testingにもよいところはあるのですが、どんな場合でもdone_testingで十分と主張するのは間違いですし、一般的にはdone_testingに頼るのではなく、明示的にplanを宣言するか、ほかの方法でテストの数を調べるほうがベターといえます。

次回は、そのような「実際に実行したテストの数」以外の方法でテストの数を調べようとするテストツールについてまとめてみましょう。

おすすめ記事

記事・ニュース一覧