let's database testing!!

  2010/10/16 YAPC::Asia 2010
          @xaicron
自己紹介


name: Yuji Shimada ( 嶋田 裕二 )
works: DeNA
twitter: @xaicron
blog: http://blog.livedoor.
jp/xaicron/
皆さん
データーベース
 処理のテスト
してますか?
DB のテストをすると


この SELECT なんにもマッチしない...
INSERT したデータが文字化けしてるや
ん!
UPDATE や DELETE に WHERE 付け忘れ
てて全件あばばば
 TRIGGER が間違ってて、データ壊れ
た。。。
という事態を未然に防げます!
というか。。。
永続的なストレージに実際に
   入出力があるんだから
テストするのはあたりまえですね!
今回は MySQL に焦点を絞って
      お話します
ORM の話もしません
基本的には 生DBI をつかってやり
       ます
Haw To Testing
鉄則
ローカルでテストできる環境を
    作ること!!
よくない例


    ステージング環境のDBに接続してテストする
    本番は MySQL だけど、テストでは SQLite を
    使う
    自分のローカルに立ち上がっている MySQL  を
    テストに使う
 
実際に存在している DB を使ってテスト
すると、毎回データが変わってテストに
ならない
本番は MySQL なのに テストでは
SQLite とかは意味がない
Test の大まかな流れ


Test::mysqld を使って毎回クリーンな mysql
を立ち上げる
Test::Fixture::DBI を使ってテストデータを
突っ込む
DB 接続して、実際にデータの入出力が行わ
れているかテスト
そして伝説へ...
Test::mysqld


new するだけで、lcoal に 新規 mysql が立ち
上がる
DESTROY が走ったら、自動的に mysql を
落とす
Author: kazuhooku
DEMO
Test::Fixture::DBI


table 作ったり、 procedure / function / trigger 作っ
たり、data ぶっこんだりできる
既存の DB から schema 定義をもってきたり、指定の
レコードをとって来たりできる
  make_*_yaml.pl で fixture や database 定義を
  YAML に吐き出せる
Author: ZIGOROu, (test: xaicron)
DEMO
なかなか便利ですが
テスト毎にこれを毎回書くのはめん
      どいので
適当に組み合わせた
Test Module を書いて使うと
      いいでしょう
Test::Foo::Fixture::DBI
  みたいな名前にして
        setup_
実際のテストの書き方
の前に
DBI を直接扱う上で
便利なモジュールを紹介します
DBI と親和性の高いモジュール



SQL::Abstract
DBIx::DBHResolver
DBIx::Connector
DBIx::Connector ->
DBIx::DBHResolver ->
        DBI
という感じでラップすると
  なかなかいい感じ
DBIx::Connector


DBI の超薄いラッパー
$dbh を局所化できる
  多分最大の利点
fixup => で再接続
トランザクション処理をよしなにやってくれる
DBIx::DBHResolver


DBI 専用の container みたいなノリ
複数の DB に接続するのに便利
  Master / Slave
  User と Diary が別の DB
Sharding していてもかなり直感的
DBIx::Connector とも協調しやすい
+SQL::Abstract
my $row = $model->connector('SLAVE')->txn(fixup => sub {
        my $dbh = shift;
        my ($stmt, @bind) = $model->sql->select(...);
        return $dbh->selectrow_hashref($stmt, undef, @bind);
});
みたないな感じでつかえる
ようやく実際のテストの話
SELECT した結果を
 返すもののテスト
my $row = try {
    $self->connector('SLAVE', $user_id)->run(fixup => sub {
        my $dbh = shift;
        my ($stmt, @bind) = $self->sql->select(
            'user_data',
            [qw/id name/],
            { id => $user_id, disabled => 0 },  
        );  
        $dbh->selectrow_hashref($stmt, undef, @bind);
    });
}
catch {
    my $e = $_;
    ...
};
mysqld に fixture を入れる
実際にたたいて値を取得
取得した値が正しいかチェック
DEMO
INSERT のテスト
try {
    my $seq = $self->next_seq('MASTER', 'seq_tweet');
    $self->connector('MASTER', $user_id)->run(fixup => sub {
        my $dbh = shift;
        my ($stmt, @bind) = $self->sql->insert(
            'tweet',
            { id => $seq, ... },
        );
        $dbh->do($stmt, undef, @bind);
        $dbh->commit;
    });
}
catch {
    my $e = $_;
};
実際にたたいて値を入力
正常に INSERT されているかチェッ
ク
UPDATE とか DELETE も
      同じ感じ
特別に難しいことは
 やっていない
すべての SQL に対して
地道にテストを書きましょう
PROCEDURE / FUNCTION
      / TRIGGER
        のテスト
FUNCTION とは



そのまんま
一連のSQLを関数化できる
CALL function_name(args) で呼べる
値を返すので、参照系に使える
PROCEDURE と



値を返さない FUNCTION
主に更新用途 
CALL procedure_name(args) で呼べる
TRIGGER とは



テーブルの更新タイミングで発火
 INSERT / UPDATE / DELETE
SQL なら何でも書ける
 PROCEDURE をよく使う (更新なので)
FUNCTION のテスト
SELECT とおんなじ
PROCEDURE / TRIGGER
      のテスト
INSERT / UPDATE /DELETE
        とおんなじ
Test::mysqld の問題点
make test したときに
すっごい時間がかかる orz
1テスト毎に mysqld を立ち上げて
  落としてるから仕方ないよね
なんとかしたい
なんとかしたい
Makefile の書き換え大変です!
Module かいたよー
Module::Install::ExtendsMakeTest
replace_default_make_test(
    includes         => ['t/lib'],
    modules          => ['Test::MyApp::DB'],
    before_run_codes => [
        sub {
            $SIG{INT} = sub { CORE::exit 1 };
            my $db = Test::MyApp::DB->setup;
            $ENV{TEST_MY_SOCKET} = $db->my_cnf->{socket};
        },  
    ],  
);
みたいな感じで書くと
make test 時に mysqld
    が立ち上がる
あとは、socket ファイルとかを指定
   して接続すれば OK
$ENV に値があったら立ち上がっ
   ている mysqld を使って、
なかったら 新規に立ち上げるよう
     にしておけばいい
DEMO
というわけで
プログラムから扱いそうな
   DB の処理は
ちゃんとテストできる!!
ので、テストを書きましょう
自分(とみんな)のために
まとめ


地道にやっていけば DB 処理の大部分をテ
スト
結構ツール類が充実してきている
でもまだ自分で書く部分が多い印象
割とこの辺のノウハウがたまりつつあるので、
よくある処理は Module 化したい
DEMO で
 使った適当なプロダクト
http://github.com/xaicron/Mayoi
ご清聴ありがとうございました
Questions?
閑話休題

Let s database_testing