« CPANで意図しない名前空間の取得を防ぐために | メイン | Teng::Plugin::SearchJoinedとSQL::Maker::JoinSelectとKyoto.pmの話 »

2013年6月28日

Test::mysqldのcopy_data_fromでテストが更に捗る話

少し前ですがTest::mysqld 0.17からは copy_data_fromというオプションが加わっています。

これは、Test::mysqld起動時にコピー元のdataディレクトリを指定できるもので MySQLの起動時間を節約することができます。テスト開始時にDBに大量のデータを 入れておきたい場合に特に有効です。

特にゲームなどの場合は、大量のマスタデータもコードの一部と言えるので、ちゃんと 全部流し込んでからテストを実施したいという要件があるので重宝します。

さて、そのdataディレクトリをどうやって作ればよいかという話になるのですが、 それも、Test::mysqldに事前に作らせてどこかに配置しておけば良いでしょう。

手順としては例えば以下のようになります。

  1. ‘tmp/test_mysqld_data’ をdatadirにしてTest::mysqldを起動
  2. DDLとマスタデータの流しこみを行う

上記を事前にやっておき、実際にテストを実施する時は、’tmp/test_mysql_data’. からcopy_data_fromします。

コードは以下の様な感じになるでしょう。

use Path::Tiny qw/path/;
use Test::mysqld;
sub prepare_mysqld_copy_data {
    my $datadir = 'tmp/test_mysqld_data';
    path($datadir)->rmtree;

    my $mysqld = Test::mysqld->new(
        base_dir => 'tmp/test_mysqld',
        my_cnf   => {
            datadir => $datadir, # $datadirを指定
            'skip-networking' => '',
        },
    ) or die $Test::mysqld::errstr;

    # データ流し込み
    deploy_test_database($mysqld->dsn);

    # ここで一旦処理終了
}

deploy_test_databaseの中見は以下の様な具合です。 スキーマの流しこみと、これまたリポジトリにコミットしてcsv管理してある マスターデータを流し込んでいます。 先日書いたDBIx::FixtureLoderが活躍していますね(ステマ)。

use DBI;
use DBIx::FixtureLoder;
sub deploy_test_database {
    my $dsn = shift;
    my $dbh = DBI->connect($dsn);

    # スキーマ流し込み
    my $source = path('sql/myapp_ddl.sql')->slurp;
    for my $stmt (split /;/, $source) {
        next unless $stmt =~ /\S/;
        $dbh->do($stmt) or die $dbh->errstr;
    }

    # マスタデータ流し込み
    my $loader = DBIx::FixtureLoder->new(dbh => $dbh);
    $loader->load_fixture('data/master/item.csv');
    $loader->load_fixture('data/master/job.csv');
    $loader->load_fixture('data/master/skill.csv');
}

これで、’tmp/test_mysqld_data’ 以下にデータが作られます。 以降は各テスト実施時に以下のようにTest::mysqldを起動すれば良いだけです。

my $mysqld = Test::mysqld->new(
    copy_data_from => 'tmp/test_mysqld_data',
    my_cnf         => { 'skip-networking' => ''},
) or die $Test::mysqld::errstr;

テストファイル毎にTest::mysqldを起動する形になりますが、 マスタデータが多い場合、

  • Test::mysqldのプロセスを使い回して起動時間を節約するよりも、データを流し込む時間を節約するほうが効果が高い
  • テストケース毎の競合を考える必要がない
  • テストでデッドロックなどが発生することを心配する必要がなく並列度を上げやすい
  • 各テストで初期化処理やクリーンアップ処理を複雑に考えずに済む

といったメリットもあるので、この形を採用するに至りました。

実際今やっている案件だと、素直にTest::mysqldを起動してデータを流し込んだ場合、10秒程度時間がかかりますが、copy_data_fromを使った場合は1秒程度に短縮されますtempfs的なものを使えばもっと速くなるでしょう。

OSXでtmpfs的なことをするのは村瀬大先生のエントリーが参考になります。

ちなみに、本来は

  • datadirがない場合のケア
  • マスタデータやDDLが更新された場合のdatadirの作り直しの処理

などが必要なので、それも考慮した形だともうちょっとコードは複雑になります。その辺の話はそのうち書くかも。また、このdatadirを元に開発用のMySQLを立てたりもしていますがその辺の話もまたそのうち。

実はこれと同様のことを自力で頑張っていたりしたので、これは神機能来たなーと、リリース直後にkazuhoさんがアナウンスする前にChanges読んで思っていたのですが、使ってみたらやっぱり神だったのでBlogろうと常々思っていてやっとblogった次第。

ちなみに最初に自力で頑張っていたのは僕ではなくて、テスト番長の335先生で、copy_data_fromがなかった時代の苦労が綴られております

彼も今年は、perl な web application のためのテスト情報という形でYAPCのトークに応募しているので、非常に楽しみですね。

投稿者 Songmu : 2013年6月28日 01:36