Database testing with MySQL::Sandbox

今日は MySQL::Sandbox を利用した MySQL を使ったデータベース処理のテストについて紹介します。

What is MySQL::Sandbox?

MySQL の実行環境を手軽に作れる CLI ツール群です。

Introduction MySQL::Sandbox

Install

これは簡単。

$ sudo cpan -i MySQL::Sandbox

Setup

その OS での MySQL を $HOME/opt/mysql にダウンロードしてから、MySQL::Sandbox に入っている make_sandbox コマンドで、

$ make_sandbox ~/opt/mysql/mysql-{version}-{dist}.tar.gz

を実行すると、Sandbox が作られて、さらに展開済みのバイナリが ~/opt/mysql 以下にバージョン番号ごとに置かれます。

使いたいバージョンの mysql を一通り揃えておくといいです。 ちなみに $HOME/opt/mysql である必要性は無いのですが、デフォルトの探索パスがここなので、このディレクトリ以下に作るのが吉です。

現在、自分の環境には、

がインストールされています。

Using MySQL::Sandbox Basis

make_sandbox コマンドの usage は以下のような感じです。

usage: ./make_sandbox version [options] {VERSION|tarball_full_name}.

ここのオプションは --help では --export-binaries しか記載されていませんが、実はもっと色々な使い方が出来ます。

例えば --sandbox-directory オプションを指定すると、好きな名前を付けて同一バージョンの Sandbox を複数作る事が出来ます。

$ make_sandbox 5.4.1 --sandbox_directory yapcasia2009 --verbose

こうすると、通常は $HOME/sandboxes/yapcasia2009 と言うディレクトリ以下に mysqld の起動に必要な各種ファイルや、便利なスクリプトが展開されます。

覚えておくべきオプションとしては、

--sandbox_directory
任意の名前の Sandbox を作成する
--no_confirm
Y/N など聞かれず全部 Y になる
--force
名前が被ったりしても無視して強制的に Sandbox を作る

辺りを覚えておけば make_sandbox では困る事は無いです。

$ ls ~/sandboxes/yapcasia2009/
USING           change_ports    data            load_grants     my.sandbox.cnf  restart         start           use
change_paths    clear           grants.mysql    my              proxy_start     send_kill       stop

my.sandbox.cnf がいわゆる my.cnf で、

[mysql]
prompt='mysql [\h] {\u} (\d) > '
#

[client]
user            = msandbox
password        = msandbox
port            = 5410
socket          = /tmp/mysql_sandbox5410.sock

[mysqld]
user                            = zigorou
port                            = 5410
socket                          = /tmp/mysql_sandbox5410.sock
basedir                         = /Users/zigorou/opt/mysql/5.4.1
datadir                         = /Users/zigorou/sandboxes/yapcasia2009/data
pid-file                        = /Users/zigorou/sandboxes/yapcasia2009/data/mysql_sandbox5410.pid
#log-slow-queries               = /Users/zigorou/sandboxes/yapcasia2009/data/msandbox-slow.log
#log                            = /Users/zigorou/sandboxes/yapcasia2009/data/msandbox.log
#
# additional options passed through 'my_clause' 
#
log-error=msandbox.err

と言う設定ファイルになっています。

とりあえずは MySQL のコマンドラインでこのインスタンスに接続したい場合は、

~/sandboxes/yapcasia2009/use

を実行すると、今しがた作った Sandbox に対して接続する事が出来ます。

と言うコマンドも含まれていますが、その名前の通りです。作成した Sandbox の mysqld を start/stop/restart します。

ところでこの Sandbox を奇麗さっぱり削除したい場合は次のようにします。

$ sbtool -o delete -s ~/sandboxes/yapcasia2009

sbtool は MySQL::Sandbox の管理ツールで、delete 以外にも様々な機能があります。

make_sandbox and single server DEMO

make_sandbox を使ったデモ

Sandbox の作成

$ make_sandbox 5.1.36 --sandbox_directory yapcasia2009 --no_confirm --force --verbose

Sandbox を使う

$ ~/sandboxes/yapcasia2009/use

Sandbox に DBI でアクセス

$ grep port ~/sandboxes/yapcasia2009/my.sandbox.cnf

でポートを確かめてから、

$ env DBI_DSN='dbi:mysql:host=localhost;port=5136;' DBI_USER=msandbox perl -MData::Dumper -MDBI -e 'my $dbh = DBI->connect; print Dumper($dbh->selectall_arrayref("show databases;"));'

Sandbox を削除

$ sbtool -o delete -s ~/sandboxes/yapcasia2009

その他

実はレプリケーション環境やら、マルチマスターなどと言った環境を好きに作る事が出来ます。

コマンドを使うとそういう事が出来ます。今回はこの説明は割愛しますが、この辺りの話は既にブログで紹介したのでそちらを見て下さい。

Problem of MySQL::Sandbox

お手軽に使える MySQL::Sandbox ですが、実は問題点も結構あります。

と言う訳でテストしたかったので、MySQL::Sandbox を操る為のモジュールを作ってみました、と言うのが今日の本題です

MySQL::Sandbox::Frontend

repository
http://github.com/zigorou/p5-mysql-sandbox-frontend/tree/master

Usage of MySQL::Sandbox

とても簡単に使う事が出来ます。

Sandbox を作成してデータベース一覧を列挙するだけのプログラムは以下のようになります。

use strict;
use Test::More;
use MySQL::Sandbox::Frontend;

my $sb = MySQL::Sandbox::Frontend->create('single', 'test', undef, +{ debug => 0, });
ok($sb, 'create sandbox');

my $dbh = $sb->dbh;
isa_ok($dbh, 'DBI::db');

my $rs = $dbh->selectall_arrayref('SHOW DATABASES;');

note(explain($rs));

is_deeply($rs, [ ['information_schema'], ['mysql'], ['test'] ], 'databases');
  
ok($sb->delete, 'delete sandbox');

done_testing;

基本的な使い方としては、1インスタンスだけを立ち上げたい時は、

MySQL::Sandbox::Frontend->create(
  'single', # 1インスタンスのみ
  'yapcasia2009', # --sandbox_directory と同じ
  '5.1.36', # バージョン
  +{ # その他のオプション
    debug => 1,
  },
);

と言う感じです。簡単ですね。

ちなみにバージョンを省略すると、探索パス上にあるバージョンで最も新しい物を自動的に選択してくれます。

またテーブル定義を行い、SHOW TABLES するサンプルはこちら。

use strict;
use Test::More;
use MySQL::Sandbox::Frontend;

my $sb = MySQL::Sandbox::Frontend->create('single', 'test', undef, +{ debug => 0, });
ok($sb, 'create sandbox');

my $dbh = $sb->dbh;
isa_ok($dbh, 'DBI::db');

diag('via hide-k blog http://blog.hide-k.net/archives/2008/06/kazuhodbic.php');

$dbh->do('create database miniblog;');
$dbh->do('use miniblog;');
$dbh->do(<< 'SCHEMA');
CREATE TABLE user (
    id int(10) unsigned NOT NULL AUTO_INCREMENT,
    screen_name varchar(255) NOT NULL,
    PRIMARY KEY (id)
) ENGINE=innodb;
SCHEMA

$dbh->do(<< 'SCHEMA');
CREATE TABLE message (
    id int(10) unsigned NOT NULL AUTO_INCREMENT,
    user_id int (10) unsigned NOT NULL,
    body varchar(255) NOT NULL,
    PRIMARY KEY (id),
    KEY userid_id_id(user_id, id)
) ENGINE=innodb;
SCHEMA

$dbh->do(<< 'SCHEMA');
CREATE TABLE follower (
    id int(10) unsigned NOT NULL AUTO_INCREMENT,
    user_id int (10) unsigned NOT NULL,
    follower_id int (10) unsigned NOT NULL,
    PRIMARY KEY (id),
    UNIQUE (user_id, follower_id),
    KEY user_id_follower_id (user_id, follower_id),
    KEY follower_id(follower_id)
) ENGINE=innodb;
SCHEMA

my $tables = $dbh->selectall_arrayref('SHOW TABLES;');

note(explain($tables));

is_deeply($tables, [ ['follower'], ['message'], ['user'] ], 'created tables');

k($sb->delete, 'delete sandbox');

done_testing;

最後にレプリケーション環境を作るサンプルはこんな感じです。

use strict;
use Test::More;
use MySQL::Sandbox::Frontend;

my $slaves = 2;

my $sb = MySQL::Sandbox::Frontend->create('replication', 'yokohama_pm', undef, +{ how_many_slaves => $slaves, debug => 0 });

is( @{$sb->nodes}, 3, 'How many nodes' );

my $dbh_master = $sb->master->dbh;
my $master_st = $dbh_master->selectrow_hashref('SHOW MASTER STATUS');

$dbh_master->do('USE test');
$dbh_master->do('CREATE TABLE IF NOT EXISTS hidek( id int primary key auto_increment, name varchar(32) ) Type=InnoDB');

note('master position: ' . $master_st->{Position});

for my $idx ( 1..$slaves ) {
    my $dbh = $sb->nodes->[$idx]->dbh;
    my $slave_st = $dbh->selectrow_hashref('SHOW SLAVE STATUS;');
    note("slave ($idx) position: " . $slave_st->{Exec_Master_Log_Pos});
    ok( $master_st->{Position} >= $slave_st->{Exec_Master_Log_Pos}, 'slave binlog position less than master' );
    $dbh->do('USE test');
    note(explain($dbh->selectall_arrayref('SHOW TABLES')));
}

$dbh_master->do('DROP TABLE hidek');

$sb->delete;

done_testing;

START/STOP SLAVE などを用いれば一意的に slave ノードの一部を切り離したりも出来ます。

SEE ALSO

TODO

などなど、引き続きのんびり開発して行こうと思います。

ご清聴ありがとうございました。