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

第27回 Test::Most:Test::Moreでは物足りなくなってきたら

この記事を読むのに必要な時間:およそ 4 分

Test::Most

Test::MoreはPerl 5.6.2からコアモジュール入りしているのですでに多くの方が利用されていると思いますが,本格的にテストを書くようになるとTest::Moreではいささか物足りない部分も出てきます。エラーや警告の出力を確認したいというのは典型的な例ですし,長いテキストや複雑なデータを比較する場合,is_deeplyなどの分析結果では不十分ということも少なくありません。

そのひとつの対策として用意されているのが,Ovidことカーティス・ポー(Curtis Poe)氏によるTest::Mostです。

これは氏が2008年にテストモジュールの利用動向を分析して選んだ利用頻度の高い5つのテストモジュール(Test::More,Test::ExceptionTest::WarnTest::DifferencesTest::Deepをまとめて読み込み,デフォルトでエクスポートされるテスト関数をすべてエクスポートしなおしてくれるというもの。

たとえば,不正な値を入れたらdieしてくれるか確認したい場合は,Test::Exception由来のdies_okでテストできますし,

use strict;
use Test::Most tests => 1;

sub func { die unless defined $_[0] }

dies_ok { func(undef) } 'undef is not allowed';

dieしないことを確認したい場合は同じくTest::Exception由来のlives_okでテストできます。

use strict;
use Test::Most tests => 1;

sub func { die unless defined $_[0] }

lives_ok { func(0) } '0 is acceptable';

入力に問題がある場合に正しく警告が出るか確認したい場合はTest::Warn経由のwarning_likeやwarnings_existを使えばよいでしょう。warnings_existは2番目の例のように複数の警告が出るかもしれない場合に便利です(ここでは「Use of uninitialized value in warn」という警告と「Warning: something's wrong」という警告が出ますが,テストでは「something's wrong」の方のみチェックしています⁠⁠。

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

warning_like   { '' . undef } qr/uninitialized/;
warnings_exist { warn undef } qr/something's wrong/;

Test::Differences由来のeq_or_diffにはあまりなじみがない方も多いかもしれませんが,テキストメインの比較的簡単なデータ構造を比較したい場合はis_deeplyよりわかりやすい出力が得られます。

use strict;
use Test::Most tests => 1;
eq_or_diff(['foo', 'bar', 'baz'], ['foo', 'baa', 'baz']);
> prove test_diff.t
test_diff.t ..
test_diff.t .. 1/1 #   Failed test at test_diff.t line 4.
# +----+-----+----------+
# | Elt|Got  |Expected  |
# +----+-----+----------+
# |   0|foo  |foo       |
# *   1|bar  |baa       *
# |   2|baz  |baz       |
# +----+-----+----------+
# Looks like you failed 1 test of 1.

さらに柔軟な比較を行いたい場合は,Test::Deep由来のcmp_deeplyを使うと,データ構造の一部のみ無視した比較なども行えるようになります。データベースをなめておかしなデータが含まれていないか確認するようなテストを書きたい場合,Test::Deep由来のヘルパー関数を利用すると非常にすっきりと書くことができます(このテストをもう少し効率よく書く方法については次回とりあげます⁠⁠。

#!perl
use strict;
use Test::Most;
use DBI;

my $dbh = DBI->connect('dbi:SQLite::memory:');
$dbh->do('create table foo (id, pass, status)');
$dbh->do('insert into foo values(?,?,?)', undef, qw/ishigaki *** 1/);
$dbh->do('insert into foo values(?,?,?)', undef, qw/charsbar *** 0/);
my $people = $dbh->selectall_arrayref('select * from foo', {Slice => +{}});

plan tests => scalar @$people;

foreach my $person (@$people) {
    cmp_deeply(
        $person,
        {
            id     => re('^\w+$'), # id
            pass   => ignore(),    # pass
            status => any(0, 1),   # status
        },
    );
}

CPANにはほかにも長文のテキストの比較に特化したTest::LongStringやバイナリデータの比較に特化したTest::BinaryDataあるいはネットワークまわりやCPANのツールチェーンまわりのテストなど,さまざまなテスト用のモジュールが用意されています。Perlが批判される原因となった旧世代のアプリケーションと,いまどきのまともなPerlアプリケーションの違いはテストが用意されているかどうかですので,今回紹介したような基本的なテストツールになじみがなかった方はこの機会にしっかり覚えておいてください。また,今回はあえてとりあげませんでしたが,Test::MoreやTest::Mostにはテストの流れを操作する仕掛けもいくつか用意されています。次回はそのような仕掛けや関連モジュールについてまとめてみる予定です。

著者プロフィール

石垣憲一(いしがきけんいち)

あるときは翻訳家。あるときはPerlプログラマ。先日『カクテルホントのうんちく話』(柴田書店)を上梓。最新刊は『ガリア戦記』(平凡社ライブラリー)。

URLhttp://d.hatena.ne.jp/charsbar/