View
1.292
Download
0
Category
Preview:
Citation preview
let's database testing!!
2010/10/16 YAPC::Asia 2010@xaicron
自己紹介
name: Yuji Shimada ( 嶋田 裕二 )
works: DeNAtwitter: @xaicronblog: 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::AbstractDBIx::DBHResolverDBIx::Connector
DBIx::Connector ->DBIx::DBHResolver ->
DBI
という感じでラップするとなかなかいい感じ
DBIx::Connector
DBI の超薄いラッパー
$dbh を局所化できる多分最大の利点
fixup => で再接続トランザクション処理をよしなにやってくれる
DBIx::DBHResolver
DBI 専用の container みたいなノリ複数の DB に接続するのに便利
Master / SlaveUser と 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 化したい
ご清聴ありがとうございました
Questions?
閑話休題
Recommended