マスタデータのテスト
ゲーム運営において必要になってくるのがアイテム定義などを含むマスタデータの管理です。ここでは、
マスタデータはゲームサーバにおいて、
マスタデータの例
筆者のプロジェクトでは、
id,name,effect_type,effect_parameter
1,小さい体力回復ドリンク,1,{"energy": 30}
2,ふつうの体力回復ドリンク,1,{"energy": 50}
3,大きい体力回復ドリンク,1,{"energy":100}
このアイテムテーブルはid
とname
とeffect_
で構成されています。id
は、name
はアイテム名です。
アイテムの種類や挙動を決めるのに、effect_
カラムとeffect_
カラムを用意しています。effect_
はプログラム内で列挙型として定義され、effect_
は、effect_
に沿って適用するときの引数で、
ほかにもアイテムのマスタデータには、
実際に使用するときには、items
テーブルに格納します。このitems
テーブルに対応するユーザーデータのテーブルをuser_
とします。user_
テーブルは、id
、user_
、item_
、having_
カラムで構成されています。
マスタデータを用いたコード
では、
回復アイテムの消費と効果をユーザーに適用する関数の例
アイテム消費APIから呼ばれる、
txn_begin; # トランザクションを開始する
# アイテムの行があればそれに対して行ロックをかける
$user_item->lock;
# アイテムを所持しているかをチェックする
$user_item->try_having_by_num($num);
# 1つずつアイテムを消費して効果を発揮させる
my $result =
$user_item->consume_and_effect(num => $num);
txn_commit; # トランザクションを終了してコミットする
実際にアイテム消費と効果適用を行う関数
アイテムを消費して効果を発揮させるconsume_
メソッドの中身を次に示します。
# アイテムを消費する
# UPDATE user_items
# SET having_num = having_num - $num
# WHERE user_id = $user_id
# AND item_id = $item_id;
$self->consume(num => $num);
# アイテムマスタの効果に対応するeffectorクラスを
# インスタンス化する
my $effector = $self->_effector;
# 保持しているMyApp::Model::Userに対して
# アイテム効果を適用させる
my $result = $effector->effect(
user => $self->user,
num => $num,
);
return $result;
マスタデータによる効果適用処理の切り替え
前項のコードで示したとおり、effect_
の値に応じて取り出します。この機構についての具体的な実装を示します。
my %effect_type_map = (
# 1: 体力を回復
1 => 'MyApp::Model::ItemEffect::RecoverEnergy',
# そのほかのtypeに応じたpackageのマッピングを羅列する
);
sub _effector {
my $self = shift;
return $effect_type_map{$self->effect_type}->new;
}
Perlであれば、
回復アイテムの効果適用の処理例
さらに、MyApp::Model::ItemEffect::RecoverEnergy
内の、
sub effect {
my ($self, %args) = @_;
my $user = $args{user};
my $parameter = $args{parameter};
my $num = $args{num};
# JSONがO/Rマッパなどのinflate機構を通してハッシュになる前提
my $energy = $parameter->{energy};
# UPDATE user
# SET energy = energy + ($energy * $num)
# WHERE id = $user_id;
my $result = $user->add_energy($energy * $num);
return $result;
}
マスタデータの内容によって、
マスタデータをテストする
マスタデータで効果を切り替えできると、
また、
そこで、
マスタデータのカラムの値域をテストする例
MySQLに入ったitems
テーブルのeffect_
の値域を、
use Test::More;
subtest 'items.effect_typeは1から3までの値を持つ' => sub {
my $rows = $dbh->selectrow_arrayref(
'SELECT * FROM items',
{ Slice => {} },
);
for my $row (@$rows) {
cmp_ok $row->{effect_type}, ">=", 1,
'items.id='.$row->{id};
cmp_ok $row->{effect_type}, "<=", 3,
'items.id='.$row->{id};
}
};
done_testing;
PostgreSQLなどではCHECK
制約があるため、CHECK
制約のないMySQLではテストでカバーします。
また、subtest
の説明や、cmp_
の説明部分も大事です。筆者のチームでは、fail
したか理解できるような説明を、
ただ、fail
するまで何のテストをしているかを読み解くことができません。Perlプログラマー以外にもわかる、
カラムの値域のチェックを宣言的に書く
そこで、Test::MasterData::Declare
を用いて、
use Test::MasterData::Declare;
master_data {
load_csv items => "master-data/csv/items.csv",
table items => "effect_type",
like_number 1 => 3;
};
done_testing;
ループなどを用いずに、fail
したときのための説明を書かなくても、fail
したかを出力してくれます。
テストするレコードを条件指定してテストする
別の例として、effect_
のときは、effect_
があって、
master_data {
load_csv items => "master-data/csv/items.csv",
table items => "effect_parameter",
if_column effect_type => 1,
json energy =>
like_number 1 => 100;
};
カラムの値が10刻みかどうかのテストをする
Perlプログラマー以外にも記述がわかりやすいことは、
簡単な例として、Test::MasterData::Declare
を使用して書くと、
master_data {
load_csv items => "master-data/csv/items.csv",
table items => "effect_parameter",
if_column effect_type => 1,
json energy =>
like_number 1 => 100,
sub { $_[0] % 10 == 0 };
};
となります。
若干Perlコードが入りましたが、
まとめ
ゲームサーバの運用と開発は、
ゲーム以外のほかの種類のサービスにも、
さて、
本誌最新号をチェック!
WEB+DB PRESS Vol.130
2022年8月24日発売
B5判/
定価1,628円
ISBN978-4-297-13000-8
- 特集1
イミュータブルデータモデルで始める
実践データモデリング
業務の複雑さをシンプルに表現! - 特集2
いまはじめるFlutter
iOS/Android両対応アプリを開発してみよう - 特集3
作って学ぶWeb3
ブロックチェーン、スマートコントラクト、 NFT