ゲームに問いかける
スナップショットでわかるのは、ものが今どこにあるかです。でもゲームで大事なのは、ものがどう動くか。そこで 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 以上上昇し、踏み切った位置に着地する」。