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

第30回 Test::Class:ユニットテストに使うだけでなく

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

メタデータからテスト件数を取得する

前回はテストファイルやテストデータの数からテストプランを計算するモジュールを紹介しました。今回はその続きとして,テストファイルのメタデータからテストの数を求めるモジュールを紹介していきましょう。これらのモジュールの多くは1994年にケント・ベック(Kent Beck)氏がSmalltalk向けに書いたSUnitを祖先にもつ,いわゆるxUnit系のフレームワークに属するものですが,Perlにはそれ以前からTest Anything Protocolを使った独自のテスト手法が存在していたため,Javaなどで使われている同種のフレームワークとはやや毛色の違う部分もあります。一般的にはクラスをひとつ書くたびに対応するユニットテスト用のクラスを書くのがよいように言われていますが,ここではもっとゆるく,テストを自動的に検出してくれるだけでなく,テストの事前事後になんらかの処理を行うときにも便利なツールという側面に注目してみることにします。

Test::Class

この手のテストモジュールとしては2000年2月にリリースされたTest::Unitが最初期のものになりますが,これは今回紹介するほかのツールに比べるとよりxUnitのやり方に忠実な分,Test::Moreを始めとするほかのテストツール群との相性は悪いため,名前のみの紹介にとどめます。

xUnit的な手法を活かしつつ,Test::Builderを利用する既存のテストツールとの親和性を高めたものとしては,エイドリアン・ハワード(Adrian Howard)氏が2002年にリリースしたTest::Classがあります。そのもっとも基本的な使い方は,このような感じになります。

use strict;
use warnings;
use Test::Class;

Test::Class->runtests;

package MyTest;
use strict;
use warnings;
use base 'Test::Class';
use Test::More;
use DBI;

sub startup : Test(startup) {
    my $self = shift;
    $self->{dbh} = DBI->connect('dbi:SQLite::memory:');
    note "startup";
}

sub setup : Test(setup) {
    my $self = shift;
    $self->{dbh}->do('create table foo (id integer primary key, text)');
    note "setup";
}

sub insert : Test {
    my $self = shift;
    ok $self->{dbh}->do('insert into foo values(?, ?)', undef, 1, 'my text');
}

sub teardown : Test(teardown) {
    my $self = shift;
    $self->{dbh}->do('drop table foo');
    note "teardown";
}

sub shutdown : Test(shutdown) {
    my $self = shift;
    $self->{dbh}->disconnect;
    note "shutdown";
}

細かな使い方についてはTest::ClassのPODをご覧いただくとして,ここではTest::ClassがMyTestパッケージ内にあるTest属性のついた5つのメソッドをかき集め,その内容を調べてテストプランを宣言し,まずはstartupのついたメソッドを,続いてsetupメソッド,特殊な指定のないふつうのテストメソッド(insertメソッド)⁠teardownメソッド,最後に後片づけ用のshutdownメソッドを実行する,という流れだけ把握しておいてください。startup/shutdownメソッドとsetup/teardownメソッドの違いは,個々のテストのたびに実行されるかどうかです。

テストを追加してみる

動作を確認するためにもうひとつ,selectというテストを追加してみましょう。ここではデータの挿入をほかのテストで流用しているため,selectテストのテストプランをその分増やしています。

sub select : Tests(2) {
    my $self = shift;
    $self->insert;
    my ($text) = $self->{dbh}->selectrow_array('select text from foo where id = ?', undef, 1);
    is $text => 'my text';
}

筆者の環境ではproveコマンドを使うといささか出力が崩れてしまいましたが,perlを使って直接テストを実行してみると,このようにsetupとteardownはそれぞれのテストの前後に実行される様子が確認できます。

> perl test.t
# startup
# setup
1..3
ok 1 - insert
# teardown
# setup
ok 2 - select
ok 3 - select
# teardown
# shutdown

テストを指定した順番で実行させてみる

このようにsetupとteardownをうまく利用して個々のテストを毎回同じ(独立した)環境でテストできるようにするのがTest::Classの基本ですが,Test::Classには個々のテストをアルファベット順に実行するという特徴があるので,それをうまく利用すると,このようにstartupからteardownまで,シナリオに沿ってテストをしていくこともできます。

sub startup : Test(startup) {
    my $self = shift;
    $self->{dbh} = DBI->connect('dbi:SQLite::memory:');
    $self->{dbh}->do('create table foo (id integer primary key, text)');
    note "startup";
}

sub test01 : Test {
    my $self = shift;
    ok $self->{dbh}->do('insert into foo values(?, ?)', undef, 1, 'my text');
}

sub test02 : Test {
    my $self = shift;
    my ($text) = $self->{dbh}->selectrow_array('select text from foo where id = ?', undef, 1);
    is $text => 'my text';
}

sub shutdown : Test(shutdown) {
    my $self = shift;
    $self->{dbh}->disconnect;
    note "shutdown";
}

なお,今回は単一のテストファイル内にテストを実行する部分(Test::Class->runtests)とテストを宣言する部分をまとめましたが,これらは別々のファイルに格納することもできますし,Test::Classのテストを書くときはむしろそうするほうが普通です。そうする場合はテストの実行部に実行したいテストパッケージをuseする文を追加してください。複数のテストパッケージをuseすればテストをまとめて実行できます(特定のテストパッケージだけを実行したい場合は,MyTest->runtestsのようにパッケージを指定することもできます)⁠

use strict;
use warnings;
use MyTest;

Test::Class->runtests;

著者プロフィール

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

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

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

コメント

コメントの記入