Фреймворки на коде
Проверка сценариями
Создайте элемент, запустите действия и проверьте каждое значение и каждую запись.
Что содержит сценарий
Сценарий это JSON-файл в scenarios/. Он создаёт элемент из входных данных, по порядку запускает действия и проверяет результаты. Манифест перечисляет все файлы сценариев, и в каждом файле один сценарий.
{
"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": [] }
]
}- Строки 4-5 Создайте элемент из этих входных данных и проверьте его начальные значения.
- Строка 6 actions перечисляет шаги для запуска по порядку.
- Строка 10 Точные записи, которые должен сделать шаг.
- Строка 17 Записей нет: ничего не изменилось.
| Поле | Значение |
|---|---|
| id | Название сценария. |
| framework | Идентификатор фреймворка, который нужно создать. |
| parameters | Входные данные для создания. |
| expectedValues | Все хранимые значения сразу после создания. |
| expectedChildren | Значения каждого дочернего слота. |
| actions | Шаги для запуска, не больше 32 на сценарий. У каждого есть действие, его параметры и expectedWrites. |
| ledger | Проверки производных значений. |
| lists | Заполняет список записями по идентификатору пресета. |
| expectedLists | Записи, которые каждый список содержит в конце, по идентификатору пресета. |
| poolRolls | Записанные броски для пула с методом roll по идентификатору пула. См. «Брошенные значения». |
| content | Записи контента, закреплённые за построенным Element (kind, entryId, name и parameters); попадают в релиз так же, как закрепления персонажа. |
Шаг может использовать и другие поля:
| Поле шага | Значение |
|---|---|
| choose | Для действия списка: запись, которую игрок выбирает для каждого входного значения ContentRef, по идентификатору пресета. |
| 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 проверяет функцию журнала. В нём указаны идентификатор функции, исходное значение и значение, которое она должна вернуть. expectedChildren проверяет дочерние элементы.
"expectedChildren": { "lamp": { "name": "Lantern", "oil": 5 } },- Строка 17 Проверяет элементы в дочерних слотах.
"ledger": [{ "function": "guard", "currentValue": 0, "expected": 3 }]- Строка 18 Вызывает функцию защиты с текущим значением 0 и ожидает 3.
Что нужно покрыть
- Для каждого конструктора, действия и функции журнала нужен сценарий.
- Каждому действию нужен хотя бы один шаг. Добавьте шаг для каждой ветки, которую хотите проверить, например один, который пишет, и один, который ничего не пишет.
- Действию, которое добавляет в список, нужны один принятый шаг и один шаг с expectedRefusal, что бы ни говорило его предложение. Отклонённый шаг доказывает, что предложение умеет отказывать. Действию, которое только удаляет из списка, нужен один принятый шаг.
- Ожидаемые значения должны совпадать с тем, что конструктор действительно возвращает, когда функции журнала уже применены.
Шаги называются по своему сценарию. Сообщение вроде heal-after-damage-2 означает третий шаг сценария heal-after-damage, потому что число считается с 0. В других сообщениях шаги считаются с 1.
Шаг, который ждёт отказа от действия, которое не было отклонено, не проходит проверку. То же верно и наоборот:
the step expects it to be refused with NOT_OFFEREDAdd 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_LIST | list.remove называет запись, которой нет в списке. |
| LIST_CAP | Будет превышен лимит, объявленный списком. |
| SAME_NAME | В списке уже есть запись с таким названием. |
Этот проект проверяет несколько из них. В нём есть и шаг, где проверка останавливает действие, поэтому шаг не ждёт ни записей, ни отказа.
{
"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"] }
}- Строки 7-12 Принято: list.add записывает верёвку.
- Строки 13-19 Отказ: список уже содержит верёвку.
- Строки 20-26 Отказ: предложение не допускает наковальню.
- Строки 35-40 В рюкзаке уже две записи, поэтому проверка ничего не пишет и отказа не ждут.
- Строки 41-48 Удалено: верёвка уходит из списка.
- Строки 49-55 Отказ: верёвки больше нет в списке.
- Строка 57 В конце в рюкзаке остаётся только свеча.
Запуск
«Проверить и показать» в The Forge компилирует проект и запускает каждый сценарий. Ничего не публикуется, и действующие персонажи не меняются.

