Perl Hackers Hub

第48回 Perlでの今風のゲームサーバ開発とテスト(2)

この記事を読むのに必要な時間:およそ 3.5 分

App::Prove::Plugin::MySQLPoolによるプーリング

mysqldの起動時間を短くする方法を挙げましたが,複数のテストファイルを実行する際に,mysqldプロセスを都度立てずに再利用することを考えてみます。筆者がメンテナーをしているApp::Prove::Plugin::MySQLPoolは,ワーカごとにmysqldを流用する機能を提供するprove向けのプラグインモジュールです。

使用するには,proveの起動時に指定します。

$ prove -PMySQLPool t/something.t

テストファイルの中では,環境変数に記述されたDSNを用いてMySQLに接続します。

my $dbh = DBI->connect($ENV{PERL_TEST_MYSQLPOOL_DSN});

テストファイルごとにmysqldが起動せず,ワーカごとに再利用できます。

mysqldのプーリングを行う際の注意点

mysqldプロセスを再利用することに関して注意点があります。それは,mysqldに保存されるテーブルやレコードも,前のテストファイルが使用したまま残っていることです。

テーブルは流用できるので,むしろ残っていたほうがテスト実行の高速化に役立つと言えますが,レコードはそうではありません。テストファイルの実行順によっては,前のテストファイルが残したレコードによってfailすることが考えられます。

そのため,テストファイル全体を単一のトランザクション内とし,テスト終了時にロールバックしてレコードの保存を避ける手法がありますが,筆者はお勧めしません。テストを行うアプリケーション内でもトランザクションが実行されており,衝突するからです。

あるいは,入れ子になったトランザクションをPerlプログラム側で実現するCPANモジュールであるDBIx::TransactionManagerを用いれば,トランザクションが衝突せずに解決できたように見えます。しかし,今度はトランザクション外でのSQL実行やトランザクションを分離したテストが実行できなくなります。

プーリングされたmysqldの初期化

そこで提案したいのが,1つのテストが終了したときに,すべてのテーブルを初期化する手法です。

以下は,レコード内のテーブルを列挙するDBIx::Inspectorを使用して,すべてのテーブルを初期化する例です。

use DBIx::Inspector;

my $inspector = DBIx::Inspector->new(dbh => $dbh);
my @tables = $inspector->tables;
for my $table (@tables) {
    my $table_name = $table->name;
    $dbh->do("TRUNCATE TABLE $table_name")
        or die $dbh->errstr;
}

トランザクションを実行してロールバックするのに比べて時間はかかりますが,テーブル内のデータを確実に削除して,実行環境をそろえることができます。

Harrietを用いてプーリングを行う

App::Prove::Plugin::MySQLPoolは簡単にmysqldプロセスを再利用できるモジュールですが,デメリットがいくつかあります。copy_data_fromなどのオプションには対応していない点,mysqldの起動とテストの実行を切り離せない点などです。

mysqldをプーリングするには,Harrietを使う方法もあります。HarrietApp::Prove::Plugin::MySQLPoolと同様,proveのプラグインとして動作するモジュールです。ただし,MySQLに特化しているのではなく,テストの前に必要なミドルウェア全般に用いることができます。

mysqld専用であるApp::Prove::Plugin::MySQLPoolと比べて,汎用的なHarrietでは余計にコードを書く必要がありますが,mysqldの使われ方を柔軟にコントロールできます。また,テスト起動時に常に立ち上げて,テストが終わると終了する使い方以外にも,あらかじめmysqldを立てておき,テストではそれを用いる使い方もできます。CIサーバではなく,ローカル開発時にテストを実行する際に,このやり方は効果を発揮します。

Harrietの使用例

使用するには,t/harriet/mysqld.plに次のコードを書きます。

use Harriet;
use Test::mysqld;

$ENV{TEST_MYSQLD_DSN} ||= do {
    my $mysqld = Test::mysqld->new(
        # 前述したスクリプトで作った
        # 事前作成ディレクトリを指定できる
        copy_data_from => ...,
    );
    $HARRIET_GUARDS::TEST_MYSQLD = $mysqld;
    $mysqld->dsn;
};

そして,テスト実行時にプラグインを指定します。

$ prove -PHarriet t/something.t

テストの中では,App::Prove::Plugin::MySQLPoolのときと同様,環境変数TEST_MYSQLD_DSNにDSNが入ります。これを用いて$dbhを作ればMySQLにつなぐことができます。

Harrietを使用した場合の注意点と解決策

ただし,これでは並列実行した際に1つのmysqldにつなぐ状態です。この問題を解決するには,

  • mysqldをt/harriet/mysqld.plの中で複数個立てておき,ワーカごとに分散させる
  • 1つのmysqldの中に複数個のデータベースを用意して,ワーカごとに使う

などの手法が考えられます。

Test::mysqldには複数個のmysqldを一気に起動するstart_mysqlsメソッドがあるため,mysqldの複数起動は容易です。

ただ,⁠ワーカごとに分散させる」が難点です。手法としては,次のものが考えられます。

  • pidで使うmysqldやデータベースを分ける
  • 何らかのデータストアに使用されているmysqldやデータベースを保存しておき,テストファイル起動時に未使用のデータベースを取得する
  • proveではない並列実行を行う独自のテストコマンドを用いて,ジョブワーカ起動時にワーカ番号を環境変数で渡し,それによって使用するmysqldやデータベースを決定する

いずれもメリット/デメリットがありますが,筆者は3番目の手法を,proveを並列に走らせて結果を統合するコマンドであるgo-proveを用いて実現しています。

<続きの(3)こちら。>

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.112

2019年8月24日発売
B5判/168ページ
定価(本体1,480円+税)
ISBN978-4-297-10787-1

  • 特集1
    React/Vue.jsで実践!
    コンポーネント設計
    モダンフロントエンドの構造化と分割の新提案
  • 特集2
    RDBMS徹底比較
    PostgreSQL,MySQL,SQL Server,Oracle Database
  • 特集3
    実践Scala
    オブジェクト指向×関数型
  • 一般記事
    自作キーボードのススメ
    デザイン,配列,打鍵感……自由自在

著者プロフィール

谷脇真琴(たにわきまこと)

1989年生まれ。山口県出身。

面白法人カヤックのゲーム事業部でサーバサイドアプリケーションの開発とワークフローの整備に携わってきた。

趣味はゲームと3Dプリンタの製作。