メインコンテンツへスキップ

コードフレームワーク

シナリオでテストする

エレメントを作り、アクションを実行して、すべての値と書き込みを確認します。

シナリオの内容

シナリオは scenarios/ にある JSON ファイルです。入力からエレメントを作り、アクションを順に実行して、結果を確認します。マニフェストはすべてのシナリオファイルを列挙し、各ファイルは 1 つのシナリオを持ちます。

scenarios/heal.json
{
  "id": "heal-after-damage",
  "framework": "keeper",
  "parameters": { "name": "Mira", "max_hp": 10 },
  "expectedValues": { "name": "Mira", "max_hp": 10, "hp": 10 },
  "actions": [
    {
      "action": "hurt",
      "parameters": { "amount": 6 },
      "expectedWrites": [{ "op": "ledger.add", "key": "hp", "value": -6 }]
    },
    {
      "action": "heal",
      "parameters": { "amount": 9 },
      "expectedWrites": [{ "op": "ledger.set", "key": "hp", "value": 10 }]
    },
    { "action": "heal", "parameters": { "amount": 3 }, "expectedWrites": [] }
  ]
}
  1. 4-5 行目 これらの入力からエレメントを作り、初期値を確認します。
  2. 6 行目 actions は、順に実行するステップを一覧にします。
  3. 10 行目 ステップが行うべき書き込みを正確に示します。
  4. 17 行目 書き込みなし:何も変わりませんでした。
フィールド意味
idシナリオの名前です。
framework作成するフレームワークの ID です。
parameters作成に使う入力です。
expectedValues構築直後のすべての保存値です。
expectedChildren各子スロットの値です。
actions実行するステップです。1 つのシナリオにつき最大 32 です。それぞれ action、parameters、expectedWrites を持ちます。
ledger派生値の確認です。
listsリストに、プリセット ID でエントリを入れておきます。
expectedLists最後に各リストが持つべきエントリを、プリセット ID で示します。
poolRollsroll メソッドを使うプールの記録されたロール(プール ID ごと)。「ロールしたスコア」を参照してください。
content構築したElementにピン留めするコンテンツ項目(kind、entryId、name、parameters)。キャラクターのピン留めと同じようにリリースに統合されます。

ステップでは、次のフィールドも使えます。

ステップのフィールド意味
chooseリストアクション用です。ContentRef の入力ごとに、プレイヤーが選ぶエントリを、プリセット ID で示します。
expectedRefusalステップが最後に受けるべき拒否です。この場合、ステップは書き込みなしを期待します。
expectedValuesAfterステップの後の保存値と派生値です。派生値は先に再計算されます。

書き込み

expectedWrites には、ステップが行うすべての書き込みを順に列挙します。設定した値には ledger.set、加算した量には ledger.add を使います。何も変更しないステップでは書き込みを列挙しないため、書き込みをスキップするガードもテストされます。

実際の書き込みが異なる場合、検証は両方のリストを表示します。

"expectedWrites": [{ "op": "ledger.set", "key": "hp", "value": 9 }]
Expected ordered writes [{"op":"ledger.set","key":"hp","value":9}]; observed [{"op":"ledger.set","key":"hp","value":10}].

派生値と子

ledger のリストはレジャー関数を確認します。関数の ID、開始する値、返すべき値を指定します。expectedChildren は子エレメントを確認します。

scenarios/new-keeper.json
  "expectedChildren": { "lamp": { "name": "Lantern", "oil": 5 } },
  1. 17 行目 子スロットにあるエレメントを確認します。
scenarios/new-keeper.json
  "ledger": [{ "function": "guard", "currentValue": 0, "expected": 3 }]
  1. 18 行目 現在の値 0 でガード関数を呼び出し、3 が返ることを期待します。

カバーすべき内容

  • すべてのコンストラクタ、アクション、レジャー関数にシナリオが必要です。
  • すべてのアクションに、少なくとも 1 つのステップが必要です。証明したい分岐ごとにステップを追加してください。たとえば、書き込むものと、何も書き込まないものです。
  • リストに追加するアクションには、オファーの内容にかかわらず、受理されるステップが 1 つと、expectedRefusal のあるステップが 1 つ必要です。拒否されるステップは、オファーが断れることを証明します。リストから削除するだけのアクションには、受理されるステップが 1 つ必要です。
  • 期待値は、レジャー関数を適用したあとの、コンストラクタが実際に返す値と一致している必要があります。

ステップの名前は、シナリオの名前から付きます。heal-after-damage-2 というメッセージは、heal-after-damage の 3 番目のステップを指します。番号が 0 から数えるためです。ほかのメッセージは、ステップを 1 から数えます。

ステップが拒否を期待しているのに、アクションが拒否されなかった場合は失敗します。逆の場合も失敗します。

the step expects it to be refused with NOT_OFFERED
Add expectedRefusal

ステップのないアクションは、次のメッセージで失敗します。

SELF_SCALAR_ACTION_SCENARIO_COVERAGE_MISSING: action keeper.hurt (Take damage) has no executable scenario steps.

受理されるステップまたは拒否されるステップが欠けているリストアクションは、次のメッセージで失敗します。

add a step with expectedRefusal, so its refused branch is proven.
add a step whose list.add is accepted, so its accepted branch is proven.

期待値がコンストラクタと異なる場合は、次のメッセージで失敗します。

Constructor output does not match its scenario.

拒否

リストアクションは拒否されることがあります。それを期待するステップは、expectedRefusal にコードを書き、書き込みは書きません。

コードいつ起きるか
NOT_OFFERED選ばれたエントリが、オファーが許可するエントリの中にありません。
ALREADY_IN_LISTリストにすでにそのエントリがあります。
LIST_FULLリストが最大サイズに達しています。
NOT_IN_LISTlist.remove が、リストにないエントリを指定しています。
LIST_CAPリストが宣言した上限を超えてしまいます。
SAME_NAMEリストにすでに同じ名前のエントリがあります。

このプロジェクトは、そのいくつかを証明しています。ガードがアクションを止めるステップもあり、そのステップは書き込みも拒否も期待しません。

scenarios/pack.json
{
  "id": "pack-trinkets",
  "framework": "keeper",
  "parameters": { "name": "Mira", "max_hp": 10 },
  "expectedValues": { "name": "Mira", "max_hp": 10, "hp": 10 },
  "actions": [
    {
      "action": "pack_trinket",
      "parameters": {},
      "choose": { "trinket": "rope" },
      "expectedWrites": [{ "op": "list.add", "key": "pack", "value": "rope" }]
    },
    {
      "action": "pack_trinket",
      "parameters": {},
      "choose": { "trinket": "rope" },
      "expectedRefusal": "ALREADY_IN_LIST",
      "expectedWrites": []
    },
    {
      "action": "pack_trinket",
      "parameters": {},
      "choose": { "trinket": "anvil" },
      "expectedRefusal": "NOT_OFFERED",
      "expectedWrites": []
    },
    {
      "action": "pack_trinket",
      "parameters": {},
      "choose": { "trinket": "candle" },
      "expectedWrites": [
        { "op": "list.add", "key": "pack", "value": "candle" }
      ]
    },
    {
      "action": "pack_trinket",
      "parameters": {},
      "choose": { "trinket": "bell" },
      "expectedWrites": []
    },
    {
      "action": "unpack_trinket",
      "parameters": {},
      "choose": { "trinket": "rope" },
      "expectedWrites": [
        { "op": "list.remove", "key": "pack", "value": "rope" }
      ]
    },
    {
      "action": "unpack_trinket",
      "parameters": {},
      "choose": { "trinket": "rope" },
      "expectedRefusal": "NOT_IN_LIST",
      "expectedWrites": []
    }
  ],
  "expectedLists": { "pack": ["candle"] }
}
  1. 7-12 行目 受理:list.add が rope を書き込みます。
  2. 13-19 行目 拒否:リストにすでに rope があります。
  3. 20-26 行目 拒否:オファーが anvil を許可しません。
  4. 35-40 行目 パックにすでに 2 つのエントリがあるので、ガードは何も書き込まず、拒否も期待されません。
  5. 41-48 行目 削除:rope がリストから外れます。
  6. 49-55 行目 拒否:rope はもうリストにありません。
  7. 57 行目 パックには candle だけが残ります。

実行する

The Forge の「検証してプレビュー」は、プロジェクトをコンパイルし、すべてのシナリオを実行します。何も公開されず、使用中のキャラクターも変更されません。

次へ