Code-Frameworks
Mit Szenarien testen
Erstelle ein Element, führe Aktionen aus und prüfe jeden Wert und jeden Schreibvorgang.
Was ein Szenario enthält
Ein Szenario ist eine JSON-Datei in scenarios/. Es erstellt ein Element aus Eingaben, führt Aktionen der Reihe nach aus und prüft die Ergebnisse. Das Manifest listet jede Szenario-Datei auf, und jede Datei enthält ein Szenario.
{
"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": [] }
]
}- Zeilen 4-5 Baue das Element aus diesen Eingaben und prüfe seine Startwerte.
- Zeile 6 actions listet die Schritte, die der Reihe nach laufen.
- Zeile 10 Die genauen Schreibvorgänge, die der Schritt machen muss.
- Zeile 17 Keine Schreibvorgänge: nichts hat sich geändert.
| Feld | Bedeutung |
|---|---|
| id | Der Name des Szenarios. |
| framework | Die ID des Frameworks, das erstellt werden soll. |
| parameters | Die Eingaben, mit denen es erstellt wird. |
| expectedValues | Jeder gespeicherte Wert direkt nach dem Erstellen. |
| expectedChildren | Die Werte jedes untergeordneten Platzes. |
| actions | Auszuführende Schritte, höchstens 32 pro Szenario. Jeder hat eine action, ihre parameters und expectedWrites. |
| ledger | Prüfungen abgeleiteter Werte. |
| lists | Füllt eine Liste vorab mit Einträgen, über die Preset-ID. |
| expectedLists | Die Einträge, die jede Liste am Ende enthält, über die Preset-ID. |
| poolRolls | Aufgezeichnete Würfe für einen Pool mit der Methode roll, nach Pool-ID. Siehe Gewürfelte Werte. |
| content | Inhaltseinträge, die an das gebaute Element angeheftet werden (kind, entryId, name und parameters) und wie die Anheftungen eines Charakters in das Release übernommen werden. |
Ein Schritt kann weitere Felder nutzen:
| Schritt-Feld | Bedeutung |
|---|---|
| choose | Für eine Listen-Aktion: der Eintrag, den der Spieler für jede ContentRef-Eingabe wählt, über die Preset-ID. |
| expectedRefusal | Die Ablehnung, mit der der Schritt enden muss. Der Schritt erwartet dann keine Schreibvorgänge. |
| expectedValuesAfter | Gespeicherte und abgeleitete Werte nach dem Schritt. Abgeleitete Werte werden zuerst neu berechnet. |
Schreibvorgänge
expectedWrites listet jeden Schreibvorgang des Schritts der Reihe nach auf. Nutze ledger.set für einen gesetzten Wert und ledger.add für einen addierten Betrag. Ein Schritt, der nichts ändert, listet keine Schreibvorgänge auf, damit auch die Bedingung getestet wird, die einen Schreibvorgang überspringt.
Weichen die echten Schreibvorgänge ab, zeigt die Prüfung beide Listen.
"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}].Abgeleitete Werte und untergeordnete Elemente
Die Liste ledger prüft eine Ledger-Funktion. Sie nennt die Funktions-ID, den Wert, von dem sie ausgeht, und den Wert, den sie zurückgeben muss. expectedChildren prüft die untergeordneten Elemente.
"expectedChildren": { "lamp": { "name": "Lantern", "oil": 5 } },- Zeile 17 Prüft die Elemente in den untergeordneten Plätzen.
"ledger": [{ "function": "guard", "currentValue": 0, "expected": 3 }]- Zeile 18 Ruft die Schutzfunktion mit dem aktuellen Wert 0 auf und erwartet 3.
Was abgedeckt sein muss
- Jeder Konstruktor, jede Aktion und jede Ledger-Funktion braucht ein Szenario.
- Jede Aktion braucht mindestens einen Schritt. Füge für jeden Zweig, den du beweisen willst, einen Schritt hinzu, zum Beispiel einen, der schreibt, und einen, der nichts schreibt.
- Eine Aktion, die zu einer Liste etwas hinzufügt, braucht einen akzeptierten Schritt und einen Schritt mit expectedRefusal, egal was ihr Angebot sagt. Der abgelehnte Schritt beweist, dass das Angebot Nein sagen kann. Eine Aktion, die aus einer Liste etwas entfernt, braucht einen akzeptierten Schritt.
- Die erwarteten Werte müssen zu dem passen, was der Konstruktor wirklich zurückgibt, mit bereits angewendeten Ledger-Funktionen.
Schritte sind nach ihrem Szenario benannt. Eine Meldung wie heal-after-damage-2 meint den dritten Schritt von heal-after-damage, denn die Zahl zählt ab 0. Andere Meldungen zählen Schritte ab 1.
Ein Schritt, der eine Ablehnung erwartet, obwohl die Aktion nicht abgelehnt wurde, schlägt fehl. Das Gegenteil schlägt auch fehl:
the step expects it to be refused with NOT_OFFEREDAdd expectedRefusalEine Aktion ohne Schritt schlägt fehl mit:
SELF_SCALAR_ACTION_SCENARIO_COVERAGE_MISSING: action keeper.hurt (Take damage) has no executable scenario steps.Eine Listen-Aktion ohne akzeptierten oder ohne abgelehnten Schritt schlägt fehl mit:
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.Erwartete Werte, die vom Konstruktor abweichen, schlagen fehl mit:
Constructor output does not match its scenario.Ablehnungen
Eine Listen-Aktion kann abgelehnt werden. Ein Schritt, der das erwartet, nennt den Code in expectedRefusal und listet keine Schreibvorgänge auf.
| Code | Wann |
|---|---|
| NOT_OFFERED | Der gewählte Eintrag gehört nicht zu den Einträgen, die das Angebot erlaubt. |
| ALREADY_IN_LIST | Die Liste enthält den Eintrag schon. |
| LIST_FULL | Die Liste hat ihre maximale Größe erreicht. |
| NOT_IN_LIST | list.remove nennt einen Eintrag, den die Liste nicht enthält. |
| LIST_CAP | Ein Limit, das die Liste deklariert, würde überschritten. |
| SAME_NAME | Die Liste enthält schon einen Eintrag mit diesem Namen. |
Dieses Projekt beweist mehrere davon. Es hat auch einen Schritt, in dem eine Schutzbedingung die Aktion stoppt, also erwartet der Schritt keine Schreibvorgänge und keine Ablehnung.
{
"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"] }
}- Zeilen 7-12 Akzeptiert: list.add schreibt das Seil.
- Zeilen 13-19 Abgelehnt: Die Liste enthält das Seil schon.
- Zeilen 20-26 Abgelehnt: Das Angebot erlaubt den Amboss nicht.
- Zeilen 35-40 Der Packen enthält schon zwei Einträge, also schreibt die Schutzbedingung nichts und es wird keine Ablehnung erwartet.
- Zeilen 41-48 Entfernt: Das Seil verlässt die Liste.
- Zeilen 49-55 Abgelehnt: Das Seil ist nicht mehr in der Liste.
- Zeile 57 Der Packen endet nur mit der Kerze.
Szenarien ausführen
Prüfen und Vorschau in The Forge kompiliert das Projekt und führt jedes Szenario aus. Nichts wird veröffentlicht, und kein laufender Charakter ändert sich.

