本文へスキップ

ゲームに問いかける ​

スナップショットでわかるのは、ものが今どこにあるかです。でもゲームで大事なのは、ものがどう動くか。そこで Keiko には、プレイの展開を見守る方法が 3 つあります。

until:あとどれくらいで? ​

until は条件が成り立つまでプレイし、かかったステップ数を返します。within(デフォルト "10s")以内に成り立たなければ、最後のステートを添えて失敗します。

ts
game.press("ArrowRight");
const steps = game.until((s) => s.player.vx >= 140, { within: "1s" });
expect(steps * game.stepSeconds).toBeBelow(0.25); // 0.25 秒未満で最高速に達する

これが手触りのテスト方法です。「15 ステップ後のプレイヤーの位置」は調整するたびに壊れますが、「プレイヤーは 0.25 秒未満で最高速に達する」こそ、本当に守りたいルールです。

text
TimeoutError: (s) => s.player.onGround didn't hold within 120 steps (2.00s).
Last state: {"player":{"x":212,"y":-40,"onGround":false}}

during:一度も起きない? ​

during はしばらくプレイし、毎ステップ後に不変条件をチェックします。false を返すか例外を投げる(なので中で expect が使えます)と失敗です。エラーにはそのステップが示されます。

ts
game.press("ArrowLeft");
game.during("3s", (s) => s.player.x >= 0, "the player stays on screen");

game.during("10s", (s) => {
  expect(s.lives).toBeAtLeast(0);
  expect(s.enemies.length).toBeAtMost(20);
});
text
InvariantError: the player stays on screen broke after 97 steps (1.62s) (tick 98).
State: {"player":{"x":-3,"y":120}}

trace:どれくらい高く、どれくらい速く? ​

trace はしばらくプレイし、毎ステップ後に値を記録します。パスか関数で指定します。

ts
game.press("Space");
const y = game.trace("player.y", "1.5s");

expect(y.start - y.min()).toBeAbove(40);    // 40 px 以上上昇する(y は下向きに増える)
expect(y.stepOf(y.min())).toBeBelow(30);    // 0.5 秒以内に頂点に達する
expect(y.last).toBe(y.start);               // 踏み切った位置に着地する

trace には start(最初のステップ前の値)、first、last、at(i)、min()、max()、range()、stepOf(valueOrPredicate)、every()、some()、changes()、timeAt(step) があり、イテレートもできます。パス指定ではデフォルトで数値を記録します。別の型なら trace<boolean>("player.onGround", "1s") のように指定します。

expect ​

マッチャー成功する条件
toBe(v)Object.is(actual, v)
toEqual(v)構造的に等しい。失敗時は最初に異なるパスを示します
toBeAbove(n), toBeBelow(n)>, <
toBeAtLeast(n), toBeAtMost(n)>=, <=
toBeBetween(min, max)両端を含む範囲
toBeCloseTo(target, tolerance = 0.001)許容誤差の範囲内
toBeTruthy(), toBeFalsy(), toBeDefined(), toBeUndefined()
toContain(item), toHaveLength(n), toMatch(re)配列と文字列
toThrow(message?)例外を投げる関数

どのマッチャーにも .not があります。ゲームの数値がぴったり一致することはまれなので、位置や速度には範囲を、個数・フラグ・名前には toBe を使いましょう。

良いゲームテストを書くには ​

  • フレームではなくルールをアサートする。「速度 ÷ 加速度以内に止まる」は調整しても生き残りますが、「tick 42 で x が 309」は生き残りません。
  • アサーションにパラメータを使う。 数値をテストにコピーせず、game.param("player.gravity") を読みましょう。
  • おもしろい場面に素早くたどり着く。 最初からプレイせず、コマンドでレベルにワープしましょう。
  • 振る舞いとしてテストに名前を付ける。 レポートが仕様書のように読めます:「押しっぱなしのジャンプは 30 px 以上上昇し、踏み切った位置に着地する」。