前回の
Test::mysqldを使ったゲームサーバのテスト
多くのWebサービスや、
代表的な手法は、
ただ、
そこで使っているのが、
Test::mysqldでテスト専用のmysqldを起動する
Test::mysqld
は、Test::mysqld
を用いてmysqldを起動するテストコードを次に示します。
use Test::mysqld;
use DBI;
use MyApp::GameLogic;
my $mysqld = Test::mysqld->new(); ―(1)
my $dbh = DBI->connect($mysqld->dsn(dbname => 'test')); ―(2)
$dbh->do($ddl_statements); ―(3)
my $game_logic = MyApp::GameLogic->new(dbh => $dbh); ―(4)
ok $game_logic->do_something;
Test::mysqld
でmysqldを起動します。そのあとに、$dbh
を作ります。ここで起動したmysqldにはデータベースtest
が存在しますが、CREATE
文などのDDL
DDLを実行したあとに、$dbh
をMySQLが使われるモデルオブジェクトに渡すと、
Test::mysqld
を使うと、
Test::mysqldの注意点
テスト専用のmysqldを起動するときに気を付けるべき点もいくつかあります。
mysqldを起動するためのリソースを余分に消費する
ローカルにある開発用のmysqldをテスト中でも用いる一般的な手法に比べ、
この問題に対処する最も良い方法は、
テストが始まるまでの起動時間が長くなる
Test::mysqld
を使うと、CREATE
文の流し込みや、
筆者の担当しているサービスのテーブル数は多く、
複数のテストを実行するときにも問題が起こります。テストファイルの中でmysqldを起動していると、
Test::mysqldの起動高速化のためにデータを流用する
mysqldの起動時間のせいで、
Test::mysqld
によるmysqldの起動には時間がかかります。その大半が、ibdata
などのデータ領域を含んだディレクトリを作成することに費やされています。このディレクトリをテスト起動時に毎回作らないようにします。あらかじめ作成しておき、
そのために、Test::mysqld
にはcopy_
オプションが存在します。これは、
以下に、
use Test::mysqld;
use File::Spec;
my $mysqld = Test::mysqld->new;
my $dbh = DBI->connect($mysqld->dsn);
$dbh->do($_) for @ddls;
$dbh->do($_) for @fixture_sqls;
$mysqld->stop;
say 'export TEST_MYSQLD_DATA_DIR='
. File::Spec->catdir($mysqld->data_dir, 'var');
このスクリプトを実行して表示された環境変数を作るコマンドを実行して、
テストコードでの例は次のようになります。
my $mysqld = Test::mysqld->new(
copy_data_from => $ENV{TEST_MYSQLD_DATA_DIR}
);
これで高速にmysqldが起動し、
App::Prove::Plugin::MySQLPoolによるプーリング
mysqldの起動時間を短くする方法を挙げましたが、App::Prove::Plugin::MySQLPool
は、prove
向けのプラグインモジュールです。
使用するには、prove
の起動時に指定します。
$ prove -PMySQLPool t/something.t
テストファイルの中では、
my $dbh = DBI->connect($ENV{PERL_TEST_MYSQLPOOL_DSN});
テストファイルごとにmysqldが起動せず、
mysqldのプーリングを行う際の注意点
mysqldプロセスを再利用することに関して注意点があります。それは、
テーブルは流用できるので、fail
することが考えられます。
そのため、
あるいは、DBIx::TransactionManager
を用いれば、
プーリングされたmysqldの初期化
そこで提案したいのが、
以下は、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_
などのオプションには対応していない点、
mysqldをプーリングするには、Harriet
を使う方法もあります。Harriet
はApp::Prove::Plugin::MySQLPool
と同様、prove
のプラグインとして動作するモジュールです。ただし、
mysqld専用であるApp::Prove::Plugin::MySQLPool
と比べて、Harriet
では余計にコードを書く必要がありますが、
Harrietの使用例
使用するには、t/
に次のコードを書きます。
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_
にDSNが入ります。これを用いて$dbh
を作ればMySQLにつなぐことができます。
Harrietを使用した場合の注意点と解決策
ただし、
- mysqldを
t/
の中で複数個立てておき、harriet/ mysqld. pl ワーカごとに分散させる - 1つのmysqldの中に複数個のデータベースを用意して、
ワーカごとに使う
などの手法が考えられます。
Test::mysqld
には複数個のmysqldを一気に起動するstart_
メソッドがあるため、
ただ、
- pidで使うmysqldやデータベースを分ける
- 何らかのデータストアに使用されているmysqldやデータベースを保存しておき、
テストファイル起動時に未使用のデータベースを取得する prove
ではない並列実行を行う独自のテストコマンドを用いて、ジョブワーカ起動時にワーカ番号を環境変数で渡し、 それによって使用するmysqldやデータベースを決定する
いずれもメリット/prove
を並列に走らせて結果を統合するコマンドであるgo-prove
を用いて実現しています。
<続きの
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/
定価1,628円
ISBN978-4-297-13000-8
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現! - 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう - 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、 NFT