日向夏特殊応援部隊

俺様向けメモ

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 失敗とかそういう状況を作れるみたいなので、網羅的にテストする事が可能だと思います。