マスタデータのテスト
ゲーム運営において必要になってくるのがアイテム定義などを含むマスタデータの管理です。ここでは、
マスタデータはゲームサーバにおいて、
マスタデータの例
筆者のプロジェクトでは、
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 


