DBD::Mock を使ったテスト
DBD::Mock は DBI のドライバの一つで、DBI を使ったプログラムで意図的な状態を作る事が出来ます。
と言う訳でメモ程度に書いて行きます。
データベースハンドルの取得
use strict; use warnings; use Test::More; use DBI; plan tests => 3; my $dbh = DBI->connect('dbi:Mock:', '', '', +{ AutoCommit => 0, RaiseError => 1 }); ok($dbh, 'Create database handle'); isa_ok($dbh, 'DBI:db'); is($dbh->{Driver}->{Name}, 'Mock', 'Driver information');
で、普通に Database Handle が取れます。
SELECT してる箇所
事前に mock_add_resultset を定義しておくと任意の resultset を返す事が出来ます。
#!/usr/bin/perl use strict; use warnings; use Test::More; use DBI; plan tests => 2; my @records = ( [1, 'zigorou'], [2, 'kazuho'], [3, 'yappo'], [4, 'tokuhirom'], [5, 'hidek'], [6, 'typester'], ); my %user_data = ( sql => q|SELECT user_id, nickname FROM user_data WHERE user_status = ?|, results => [ [qw/user_id nickname/], @records, ], ); my $dbh = DBI->connect('dbi:Mock:', '', '', +{ AutoCommit => 0, RaiseError => 1, }); $dbh->{mock_add_resultset} = \%user_data; my $sth = $dbh->prepare($user_data{sql}); isa_ok($sth, 'DBI::st'); $sth->execute(1); is_deeply($sth->fetchall_arrayref, \@records, 'resultset');
INSERT, UPDATE, DELETE とか
$sth->rows で返って来る件数を指定するのも mock_add_resultset で定義出来ます。
#!/usr/bin/perl use strict; use warnings; use Test::More; use DBI; plan tests => 2; my %user_data = ( sql => q|INSERT INTO user_data(nickname) VALUES(?)|, results => [ [qw/rows/], [], ], ); my $dbh = DBI->connect('dbi:Mock:', '', '', +{ AutoCommit => 0, RaiseError => 1, }); $dbh->{mock_add_resultset} = \%user_data; my $sth = $dbh->prepare($user_data{sql}); isa_ok($sth, 'DBI::st'); $sth->execute('zigorou'); is_deeply($sth->rows, 1, 'affected rows'); $dbh->commit;
追記1 (2009-03-25T17:23:01+09:00)
DBI::connect(), DBI::st->prepare(), DBI::st->execute() でわざと失敗する例
use Test::More; use Test::Exception; use Carp; use DBI; plan tests => 9; my $drh = DBI->install_driver('Mock'); isa_ok($drh, 'DBI::dr'); dies_ok( sub { local $drh->{mock_connect_fail} = 1; my $dbh = DBI->connect('dbi:Mock:', '', '', +{ AutoCommit => 0, RaiseError => 1, }) || croak(q|Cannot connect mock database|); }, 'mock_connect_fail on' ); lives_ok( sub { local $drh->{mock_connect_fail} = 0; my $dbh = DBI->connect('dbi:Mock:', '', '', +{ AutoCommit => 0, RaiseError => 1, }) || croak(q|Cannot connect mock database|); }, 'mock_connect_fail off' ); my $dbh = DBI->connect('dbi:Mock:', '', '', +{ AutoCommit => 0, RaiseError => 1, }) || croak(q|Cannot connect mock database|); isa_ok($dbh, 'DBI::db'); lives_ok( sub { local $dbh->{mock_can_prepare} = 1; my $sth = $dbh->prepare(q|SELECT * FROM foo;|); }, 'mock_can_prepare on' ); dies_ok( sub { local $dbh->{mock_can_prepare} = 0; my $sth = $dbh->prepare(q|SELECT * FROM foo;|); }, 'mock_can_prepare off' ); my $sth = $dbh->prepare(q|SELECT * FROM foo;|); isa_ok($sth, 'DBI::st'); lives_ok( sub { local $dbh->{mock_can_execute} = 1; $sth->execute(); }, 'mock_can_execute on' ); dies_ok( sub { local $dbh->{mock_can_execute} = 0; $sth->execute(); }, 'mock_can_execute off' );
まとめ
とりあえず上手い事、データベース処理を差し替えてあげて DBD::Mock のデータベースハンドルを作ってやって、外側から resultset を定義してあげたりすると、いい感じでテストを実行出来ます。
また他にも様々な機能を持っていて、意図的に commit 失敗とかそういう状況を作れるみたいなので、網羅的にテストする事が可能だと思います。