Perl Hackers Hub

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

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

前回の(1)こちらから。

Test::mysqldを使ったゲームサーバのテスト

多くのWebサービスや,ソーシャルゲームアプリのサーバ内のデータストアに用いられているMySQLですが,MySQLを使う機能を含んだアプリケーションのテストにはいくつか手法があります。

代表的な手法は,直接MySQLを使うアプリケーション内のレイヤと,そのレイヤを使う別のレイヤとの関係を,依存性注入などの手法により疎結合にする方法です。テストではMySQLを直接使うのではなく,モック化されたレイヤに入れ替えます。

ただ,この手法は,筆者の担当しているサービスではMySQLを使うレイヤを分離するのが難しく,採用できませんでした。

そこで使っているのが,テスト専用のmysqldです。テスト専用のmysqldを使うことで,テストを実行する環境に依存せず,テストの並列実行も可能になります。

Test::mysqldでテスト専用のmysqldを起動する

Test::mysqldは,コード中で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;

(1)で,まずTest::mysqldでmysqldを起動します。そのあとに,(2)で起動したmysqldのDSNData Source Nameを用いて$dbhを作ります。ここで起動したmysqldにはデータベースtestが存在しますが,その中のテーブル定義は空です。ですので(3)のように,CREATE文などのDDLData Definition Languageを実行します。

DDLを実行したあとに,(4)のように$dbhをMySQLが使われるモデルオブジェクトに渡すと,テスト中では起動したmysqldが使われます。

Test::mysqldを使うと,実行される環境にあらかじめmysqldを立てておく手間がなくなります。そして,すでに立っているmysqldの状態に依存しなくなります。常に初期化されたmysqldが使われることで,テストの結果が揺れるなどの,mysqldを使ったテストでありがちな問題が起こりません。また,独自のmysqldを起動することで,テスト向けにチューニングされた設定を常に使えます。

Test::mysqldの注意点

テスト専用のmysqldを起動するときに気を付けるべき点もいくつかあります。

mysqldを起動するためのリソースを余分に消費する

ローカルにある開発用のmysqldをテスト中でも用いる一般的な手法に比べ,専用のmysqldを起動する手法では,mysqldが2つ以上立つことになり,テスト実行マシン上のCPUやメモリ,ディスクなどを余計に消費します。テストを並列実行しないのであれば問題になることはありませんが,並列実行の際に1テストワーカあたり1つのmysqldを割り当てる実装にすると,大きな問題になります。

この問題に対処する最も良い方法は,フルテストでは良いスペックのCIContinuous Integration継続的インテグレーション)サーバを用いることです。また,1つのmysqldに1つのデータベースを収容するのではなく,複数のデータベースを受け持って,起動するmysqldの数を抑えることも考えられます。

テストが始まるまでの起動時間が長くなる

Test::mysqldを使うと,テーブルやレコードが入っていない空のmysqldが起動します。テストに用いるためには,CREATE文の流し込みや,マスタデータやテストデータのローディングが必要となります。

筆者の担当しているサービスのテーブル数は多く,DDLの実行だけでも多くの時間を使います。また,マスタデータも大量にあるため,マスタデータの入力もテスト実行時のオーバーヘッドとなります。

複数のテストを実行するときにも問題が起こります。テストファイルの中でmysqldを起動していると,mysqldはテストファイルの実行が終わったあとすぐに終了します。そして,ほかのテストファイルを実行する際にもまたmysqldを起動する作業が発生します。このままではテストコード実行よりも,テストを実行するためにmysqldを準備する時間のほうが多くなります。

Test::mysqldの起動高速化のためにデータを流用する

mysqldの起動時間のせいで,テストの実行時間が長くなってしまう問題を解決する方法を考えてみます。

Test::mysqldによるmysqldの起動には時間がかかります。その大半が,一時ディレクトリにibdataなどのデータ領域を含んだディレクトリを作成することに費やされています。このディレクトリをテスト起動時に毎回作らないようにします。あらかじめ作成しておき,できるだけ再利用して高速化を図ります。

そのために,Test::mysqldにはcopy_data_fromオプションが存在します。これは,あらかじめ作成したMySQLのデータディレクトリを保存しておき,オプションにディレクトリパスを指定することで,mysqldの起動時にコピーして使用する機能です。

以下に,データディレクトリを作るスクリプトと,それを使う例を示します。

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が起動し,すぐにテストが実行されます。

著者プロフィール

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

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

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

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

コメント

コメントの記入