ソースコード解説・目次

CHAPTER 20 · 全23項目

ゲームロジックのテスト

ブラウザ部分を置き換え、本番のJavaScriptに対してルールを検証します。

主な参照先:tests/helpers/game.cjs / node:test / vm

本体からスクリプトを取り出して実行する

tests/helpers/game.cjs · L5–6ソースを見る ↗
const html = readFileSync(join(__dirname, "..", "..", "game.html"), "utf8");
const source = html.match(/<script>([\s\S]*?)<\/script>/)[1];

テスト用にゲームを別実装するのではなく、game.html の script 内を読み込んで Node.js の vm で実行します。Canvas、DOM、音声、時刻、フレームの予約などは簡単な代用品へ置き換えます。ゲームのルールを担う関数は本番のものです。読み込んだ後は startGame() を呼び、敵の射撃・ボーナス円盤・突入のタイマーを Infinity にして、偶然の出来事がテストに割り込まないようにします。Bカプセルを指定した回数だけ拾わせる collectBeam(times = 1) もここで定義しており、collectBeam(4) で連結砲を4門そろえられます。

状態を用意し、操作して、結果を調べる

tests/arcade-features.test.cjs · L50–65ソースを見る ↗
test("a bomb clears bullets, destroys nearby enemies, preserves distant enemies and cover, and grants invulnerability", () => {
  const run = game();
  run(`
    aliens[0].flight = { x: player.x, y: PLAYER_Y - 80 };
    const cover = JSON.stringify(barriers);
    eBullets = [{ x: player.x, y: PLAYER_Y, w: 3, h: 9, vy: 0 }];
  `);
  assert.equal(run("activateBomb()"), true);
  assert.equal(run("special.bombs"), 1);
  assert.equal(run("eBullets.length"), 0);
  assert.equal(run("aliens[0].alive"), false);
  assert.equal(run("aliens[1].alive"), true);
  assert.equal(run("JSON.stringify(barriers) === cover"), true);
  assert.ok(run("player.inv > 1"));
  assert.equal(run("activateBomb()"), false);
});

このテストは近い敵・遠い敵・敵弾・バリアを用意し、ボムを使った結果を調べています。近い敵だけが消え、敵弾が空になり、バリアが保持され、無敵時間が付くことを検証します。直後の再使用が拒否されることまで含めて仕様を表しています。

実行する

リポジトリのルートで実行
node --test tests/*.test.cjs

2つのファイルに合計44個のテストがあります。

ファイル主な対象
tests/beam-link.test.cjs連結砲の取得(1個で1門)・射撃(1門につき画面内1発)・併用・バリアの素通り・持ち越し・画面端・ミスで2門減(9個)
tests/arcade-features.test.cjsレーザー(照射中の減速を含む)・ボム・ボス(レベルごとの攻撃)・コンボの段・ウェーブクリア後の自動進行・後半の射撃(狙い撃ち・突入敵の射撃・3方向弾・予告)・突入・入力・1UP・弾のすり抜け防止・連続ウェーブ・ミス時の円盤の効果音・パーティクルの上限・ドット文字・アドレスによる表示言語の選択(35個)

このテストでは分からないこと

Canvasの描画命令と音声出力を置き換えているため、実際の絵の崩れ、文字の見切れ、音の聴こえ方、描画速度は確認できません。ロジックのテストに加え、ブラウザで通常プレイ・ウェーブの切り替わり・ボス戦・ポーズを確認します。