framework/skills/bsl-practices/test-writing/SKILL.md
Для YaxUnit-тестов BSL, моков и assertions
npx skillsauth add steelmorgan/1c-agent-based-dev-framework test-writingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Запуск написанных тестов — отдельный навык test-execution.
Полная документация YaxUnit: см. references/yaxunit-cheatsheet.md
Тесты хранятся в отдельном расширении конфигурации: <корень проекта>/exts/TESTS/
| Формат | Код модуля | Файл метаданных |
|--------|------------|-----------------|
| EDT | exts/TESTS/src/CommonModules/<ИмяМодуля>/Module.bsl | .../<ИмяМодуля>.mdo |
| DESIGNER | exts/TESTS/src/CommonModules/<ИмяМодуля>/Ext/Module.bsl | .../<ИмяМодуля>.xml |
Если формат неочевиден — проверь application-*.yml / yaxunit-*.yml в корне проекта.
Не смешивай структуры: для DESIGNER обязателен Ext/, для EDT он не используется.
Новый модуль должен быть зарегистрирован в Configuration.[mdo|xml], иначе тест не подхватится раннером.
Категорические запреты:
exts/TESTS/**, никогда в основной конфигурацииexts/YAXUNIT/** никогда не изменяется вручную — это инфраструктура раннераШаблон: <Префикс>_<ИмяОбъекта>[_<Суффикс>]
| Тип объекта | Префикс | Пример |
|-------------|---------|--------|
| Общий модуль | ОМ_ | ОМ_ОбщегоНазначения |
| Документ | Док_ | Док_ПоступлениеТоваров |
| Справочник | Спр_ | Спр_Контрагенты |
| Регистр накопления | РН_ | РН_ОстаткиТоваров |
| Регистр сведений | РС_ | РС_КурсыВалют |
| Обработка | Обр_ | Обр_ЗакрытиеМесяца |
| Тип модуля | Суффикс | Пример |
|-----------|---------|--------|
| Модуль объекта | _МО | Спр_Контрагенты_МО |
| Модуль менеджера | _ММ | РН_ОстаткиТоваров_ММ |
| Модуль набора записей | _НЗ | РБ_Хозрасчетный_НЗ |
Иногда YaxUnit используют не как регрессионный тест, а как одноразовый серверный канал для ручной production-операции: исправить данные, перепровести точечный набор документов, выполнить контролируемую миграцию. Такой модуль НЕ является обычным тестом и не должен случайно попасть в режим «запустить все тесты».
| Что маркировать | Конвенция |
|-----------------|-----------|
| Имя модуля | Опер_<Описание>[_Номер] или проектный префикс + явный фрагмент _Операция_; не маскировать под обычный _Тест |
| Заголовок модуля | Комментарий первой строкой: // ONE_OFF_YAXUNIT_OPERATION: НЕ ЗАПУСКАТЬ В ОБЩЕМ ПРОГОНЕ. <назначение> |
| Имя набора | Префикс [ONE_OFF_OPERATION] <краткое назначение> |
| Теги YaxUnit | .Тег("one-off-operation") на наборе и, если тесты регистрируются отдельно, на каждом операционном тесте |
| Контекст запуска | Комментарий рядом с регистрацией: кто разрешил операцию, на какой базе/контуре можно запускать, как проверить результат и как вывести модуль из общего прогона после выполнения |
Маркер и тег — это навигация, а не защита. Операционный модуль MUST иметь технический барьер, из-за которого обычный all-tests не регистрирует и не выполняет операцию:
ИсполняемыеСценарии() MUST возвращаться без ДобавитьТестовыйНабор() без явного opt-in. Opt-in задаётся отдельным параметром запуска/настройкой/обёрткой и документируется в комментарии у регистрации. Обычный «запустить все тесты» этот opt-in не устанавливает.one-off-operation, после отдельного подтверждения оператора. Запуск без фильтра модулей/методов запрещён.// ONE_OFF_YAXUNIT_OPERATION: НЕ ЗАПУСКАТЬ В ОБЩЕМ ПРОГОНЕ. Разовая корректировка данных.
Процедура ИсполняемыеСценарии() Экспорт
Если НЕ РазовыйОперационныйПрогонРазрешён() Тогда
Возврат;
КонецЕсли;
ЮТТесты
.ДобавитьТестовыйНабор("[ONE_OFF_OPERATION] Корректировка данных")
.Тег("one-off-operation")
.ДобавитьСерверныйТест("ВыполнитьКорректировку");
КонецПроцедуры
Обязательно: экспортная процедура ИсполняемыеСценарии. Только регистрация тестов — никаких данных, никакой логики.
Процедура ИсполняемыеСценарии() Экспорт
ЮТТесты
.ДобавитьТестовыйНабор("Остатки")
.ДобавитьСерверныйТест("ТестПолучитьОстатки")
.ДобавитьСерверныйТест("ТестОстатокПустойСклад")
.ДобавитьТестовыйНабор("Перемещение")
.ДобавитьСерверныйТест("ТестПеремещениеМеждуСкладами");
КонецПроцедуры
| Метод | Где выполняется |
|-------|----------------|
| ДобавитьТест | контекст по умолчанию |
| ДобавитьСерверныйТест | &НаСервереБезКонтекста |
| ДобавитьКлиентскийТест | &НаКлиенте |
Любое изменение серверной логики или серверного контекста ОБЯЗАНО иметь YaxUnit-проверку на том же runtime-слое. Это относится к общим модулям, модулям менеджеров/объектов, серверным методам форм, запросам, записи регистров/документов, фоновым и регламентным обработчикам, если проверяемый эффект доступен из серверного теста.
Правило выбора:
| Ситуация | Действие | |----------|----------| | Изменён существующий серверный метод, и тест уже есть | Актуализировать тест под новое поведение и перепрогнать его. | | Изменён существующий серверный метод, теста нет | Добавить минимальный YaxUnit-тест на изменённый контракт. | | Добавлен новый серверный метод/API | Добавить YaxUnit-тест вместе с методом. | | Изменение серверной логики проявляется только через процесс | Написать серверный integration-тест на наблюдаемый эффект процесса или явно зафиксировать, почему нужен более высокий сценарный уровень. |
Синтаксис, LSP и успешная сборка НЕ заменяют YaxUnit для серверной логики: они подтверждают, что код может быть загружен, но не подтверждают контракт метода. Если тест технически невозможен, это фиксируется как blocker/остаточный риск с причиной; молчаливый пропуск запрещён.
Один тест проверяет одно утверждение. Паттерн Arrange-Act-Assert:
Процедура ТестПолучитьОстатки() Экспорт
// Arrange
Склад = ЮТест.Данные().СоздатьЭлемент("Справочник.Склады");
НоменклатураСсылка = ЮТест.Данные().СоздатьЭлемент("Справочник.Номенклатура");
// Act
Остаток = УправлениеСкладом.ПолучитьОстаток(НоменклатураСсылка, Склад);
// Assert
ЮТест.ОжидаетЧто(Остаток).Равно(0);
КонецПроцедуры
// Базовые сравнения
ЮТест.ОжидаетЧто(Результат).Равно(42);
ЮТест.ОжидаетЧто(Результат).НеРавно(0);
ЮТест.ОжидаетЧто(Результат).Больше(10);
ЮТест.ОжидаетЧто(Флаг).ЭтоИстина();
ЮТест.ОжидаетЧто(Значение).ВСписке(МассивДопустимых);
// Тип и заполненность
ЮТест.ОжидаетЧто(Ссылка).ИмеетТип("СправочникСсылка.Номенклатура");
ЮТест.ОжидаетЧто(Значение).НеЯвляетсяНеопределено();
// Исключения
ЮТест.ОжидаетЧто(ЭтотОбъект).МетодВыбрасываетИсключение("МетодСОшибкой", Параметры);
// Данные ИБ
ЮТест.ОжидаетЧтоТаблицаБазы("Справочник.Склады")
.СодержитЗаписи()
.ГдеРеквизит("Наименование").Равно("Основной склад");
// Пустышка
Склад = ЮТест.Данные().СоздатьЭлемент("Справочник.Склады");
// Конструктор с реквизитами
Номенклатура = ЮТест.Данные()
.КонструкторОбъекта("Справочник.Номенклатура")
.Установить("Наименование", "Тестовый товар")
.Установить("ЕдиницаИзмерения", ПредопределенноеЗначение("Справочник.ЕдиницыИзмерения.Штука"))
.Записать()
.Ссылка();
// Документ
Документ = ЮТест.Данные().СоздатьДокумент("Документ.ПоступлениеТоваров");
Данные через ЮТест.Данные() автоматически удаляются после теста. Не создавай данные в ИсполняемыеСценарии.
Тестовый объект обязан быть валиден так же, как боевой. Неполный тест-объект либо падает на ПроверитьЗаполнение()/проведении, либо записывается семантически неполным и даёт ложно-зелёный результат (тест проходит на данных, которых в реальности не бывает).
| Требование | Правило |
|---|---|
| Владелец у подчинённых справочников | Справочник, подчинённый владельцу (в метаданных задано Подчинение/Владельцы), в тестовых данных ВСЕГДА заполняет Владелец. Подчинённый элемент без владельца семантически невалиден; контроль уникальности кода ведётся в разрезе владельца (отсюда коллизии Код не уникально); запросы и зачистка по владельцу на таком элементе ломаются. |
| Все обязательные реквизиты | Заполнять ВСЕ реквизиты, у которых в метаданных Проверка заполнения = Выдавать ошибку (FillChecking = ShowError), плюс обязательные стандартные реквизиты. |
| Обязательные стандартные реквизиты | Справочник: Наименование/Код, если помечены проверкой заполнения; подчинённый — Владелец. Документ: Дата (и Номер, если без автонумерации). Набор записей РС: ВСЕ измерения. |
| Источник истины — метаданные, НЕ соседний тест | Перед созданием тест-объекта свериться с описанием метаданных (get_metadata_structure / конфигуратор): какие реквизиты ShowError, есть ли подчинение. Копировать набор полей из соседнего теста без сверки запрещено — у объекта мог появиться новый обязательный реквизит. |
Почему по метаданным, а не по примеру: обязательность поля — свойство объекта (Проверка заполнения), оно меняется при доработке конфигурации. Тест, заполнявший поля «как сосед», молча перестаёт покрывать новый обязательный реквизит — и либо падает на проведении, либо пишет неполные данные. Сверка с описанием метаданных делает набор полей самоактуализирующимся.
// Подчинённый справочник: Владелец ОБЯЗАТЕЛЕН (Договор подчинён Контрагенту)
Контрагент = ЮТест.Данные()
.КонструкторОбъекта("Справочник.Контрагенты")
.Установить("Наименование", "Тестовый контрагент") // ShowError-реквизит
.Записать()
.Ссылка();
Договор = ЮТест.Данные()
.КонструкторОбъекта("Справочник.ДоговорыКонтрагентов")
.Установить("Владелец", Контрагент) // подчинение: без владельца невалидно
.Установить("Наименование", "Тестовый договор") // ShowError-реквизит
.Записать()
.Ссылка();
Паттерн: Обучение -> Прогон -> Проверка.
Процедура ТестРасчётСкидки() Экспорт
Мокито.Обучение(МодульСкидок)
.Когда().ПолучитьПроцентСкидки(Клиент)
.Вернуть(15);
Результат = УправлениеПродажами.РассчитатьСумму(100, Клиент);
ЮТест.ОжидаетЧто(Результат).Равно(85);
Мокито.Проверить(МодульСкидок).ПолучитьПроцентСкидки(Клиент);
КонецПроцедуры
Мокито.Обучение(Модуль).Когда().МетодА(Параметр).Вернуть(42);
Мокито.Обучение(Модуль).Когда().МетодБ(Параметр).ВыброситьИсключение("Текст ошибки");
Мокито.Обучение(Модуль).Когда().МетодВ().Пропустить();
Мокито.Обучение(Модуль).Когда().МетодГ().Наблюдать();
Процедура ИсполняемыеСценарии() Экспорт
ЮТТесты
.ДобавитьТестовыйНабор("Расчёты")
.Перед("ПередНаборомРасчёты")
.После("ПослеКаждогоТестаОчистка")
.ДобавитьСерверныйТест("ТестРасчётА")
.ДобавитьСерверныйТест("ТестРасчётБ");
КонецПроцедуры
Процедура ПередНаборомРасчёты() Экспорт
ЮТест.Контекст().УстановитьЗначение("Ставка", 18);
КонецПроцедуры
ЮТест.Контекст().УстановитьЗначение("МоёЗначение", Данные);
Данные = ЮТест.Контекст().Значение("МоёЗначение");
Процедура ИсполняемыеСценарии() Экспорт
Варианты = ЮТест.Варианты()
.Добавить(0, "Нулевое количество", 0)
.Добавить(10, "Положительное", 100)
.Добавить(-5, "Отрицательное", 0);
ЮТТесты
.ДобавитьТестовыйНабор("Расчёт суммы")
.ДобавитьСерверныйТест("ТестРасчётСуммы")
.СПараметрами(Варианты);
КонецПроцедуры
Процедура ТестРасчётСуммы(Количество, Описание, ОжидаемаяСумма) Экспорт
Результат = МойМодуль.РассчитатьСумму(Количество);
ЮТест.ОжидаетЧто(Результат)
.НазваниеПроверки(Описание)
.Равно(ОжидаемаяСумма);
КонецПроцедуры
Тест, пишущий в БД, обязан откатывать свои изменения без остатка. Без изоляции каждый прогон оставляет мусор в базе, тесты теряют идемпотентность. Базовый шаблон .ВТранзакции() (вызывается сразу после ДобавитьТестовыйНабор(), применяется на уровне набора: рантайм ищет fluent-настройку по иерархии Тест → Набор → Модуль), три исключения из него и паттерн перечитывания при перепроведении — канон в references/yaxunit-cheatsheet.md («Шпаргалка: изоляция пишущих тестов»). Здесь — только то, что не покрыто шпаргалкой: коллектор тестовых объектов, специфика исключений и приёмка изоляции по дельте.
Правило
test-zero-residueтребует: тест, генерирующий данные, регистрирует КАЖДЫЙ созданный объект в коллекторе в момент создания; teardown проходит по коллектору и физически удаляет всё, что пережило откат транзакции. Это основной штатный механизм зачистки — НЕ перебор базы по именам/префиксам.
Почему коллектор, а не автотрекинг ЮТест.Данные(): автоудаление ЮТест.Данные() срабатывает ТОЛЬКО если в наборе вызван .УдалениеТестовыхДанных(). Объекты, созданные через КонструкторОбъекта(...).Записать(), Документы.X.СоздатьДокумент(), Справочники.X.СоздатьЭлемент() или из хелперов, НЕ трекаются вовсе. Коллектор покрывает ВСЕ пути создания единообразно — точными ссылками, без догадок.
Контракт модуля-коллектора (общий серверный модуль, напр. биг_ТестовыйКоллектор):
Зарегистрировать(Ссылка) Экспорт — вызывается сразу после КАЖДОГО создания (справочник, документ, субаккаунт, набор-владелец и т.д.).ОчиститьВсё() Экспорт — в teardown (.После() / финальный шаг сценария / ПослеВсехТестов()): LIFO-обход (зависимые → владельцы); Объект = Ссылка.ПолучитьОбъект(); если Неопределено (пережил откат .ВТранзакции() или снят каскадом) → пропуск; иначе Объект.ОбменДанными.Загрузка = Истина; Объект.Удалить(); под Попытка + лог. В конце — обнулить накопитель.Перем модуля уровня сеанса в общем серверном модуле НЕ переживает между отдельными НаСервереБезКонтекста-вызовами, которыми YaxUnit запускает каждый тест. Накопитель хранить в ХранилищеОбщихНастроек (или эквиваленте, переживающем вызовы), не в Перем.Для в 1С считает ТОЛЬКО вверх — шага вниз нет. Для Сч = Накопитель.ВГраница() По 0 Цикл НЕ исполняет тело НИ РАЗУ (условие ВГраница() <= 0 ложно сразу при непустом накопителе) — это МОЛЧАЛИВЫЙ no-op: тест зелёный, лог чистый, а residue копится (прецедент GBIG PAM: удалено=0 при 40 в накопителе). Обратный обход делать ТОЛЬКО через Пока с ручным декрементом ДО любого Продолжить.// создание — сразу регистрируем
Портфель = ЮТест.Данные().СоздатьЭлемент("Справочник.биг_Портфели").Установить(...).Объект().Ссылка;
биг_ТестовыйКоллектор.Зарегистрировать(Портфель);
...
// teardown — один вызов на весь накопитель
Процедура ПослеВсехТестов() Экспорт
биг_ТестовыйКоллектор.ОчиститьВсё();
КонецПроцедуры
Канонический обратный обход в ОчиститьВсё() (LIFO: зависимые раньше владельцев):
Процедура ОчиститьВсё() Экспорт
Накопитель = ПрочитатьНакопитель();
// ВАЖНО: `Пока` с декрементом, а НЕ `Для ... По 0` (тот не исполнится — см. ловушку выше).
Сч = Накопитель.ВГраница();
Пока Сч >= 0 Цикл
Ссылка = Накопитель[Сч];
Сч = Сч - 1; // декремент ДО `Продолжить`, иначе вечный цикл
Если НЕ ЗначениеЗаполнено(Ссылка) Тогда
Продолжить;
КонецЕсли;
Попытка
Объект = Ссылка.ПолучитьОбъект();
Если Объект <> Неопределено Тогда // Неопределено = пережил откат / снят каскадом -> норма
Объект.ОбменДанными.Загрузка = Истина; // обход FillChecking/проведения при физ. удалении
Объект.Удалить();
КонецЕсли;
Исключение
ЗаписьЖурналаРегистрации("ТестовыйКоллектор", УровеньЖурналаРегистрации.Предупреждение,
, Ссылка, ОписаниеОшибки()); // утечку видно в ЖР, но teardown не валим
КонецПопытки;
КонецЦикла;
Сбросить(); // обнулить накопитель -> следующий модуль стартует с пустого
КонецПроцедуры
Приёмка коллектора: не «тест зелёный», а ДЕЛЬТА-0 — счётчики затронутых справочников/документов/регистров до и после прогона равны. Зелёный тест при сломанном teardown — типовая маскировка (loop no-op выше). Проверять дельту запросом КОЛИЧЕСТВО(*) до/после, а изменения BSL грузить через full-rebuild (динамический build — no-op на BSL, residue прежнего прогона создаёт ложную картину).
Элементы справочников создавать через ЮТест.Данные().СоздатьЭлемент(...) или КонструкторОбъекта(...).Записать() и сразу регистрировать в коллекторе. Важно: КонструкторОбъекта(...).Записать() и прямой Справочники.X.СоздатьЭлемент() YaxUnit НЕ трекает (остаются в базе) — для них коллектор обязателен; даже ЮТест.Данные() без .УдалениеТестовыхДанных() не самоудаляется. Прямой Справочники.X.СоздатьЭлемент() мимо коллектора — антипаттерн.
СоздатьДокумент() — обязательный teardownЮТест.Данные().СоздатьДокумент(...) трекается и удаляется автоматически. Но если документ создаётся через Документы.X.СоздатьДокумент() напрямую — он НЕ трекается, нужен явный teardown в .После("ИмяПроцедурыОчистки").
.ВТранзакции() — спецификаШаблоны кода для трёх исключений и паттерн перечитывания объекта при перепроведении (включая ограничение платформы 8.3.27, [ОшибкаХранимыхДанных]) — в references/yaxunit-cheatsheet.md. При каждом исключении обязателен комментарий-обоснование у набора и teardown через .После(); ниже — причина исключения сверх того, что есть в шпаргалке:
| Ситуация | Причина исключения | Способ изоляции |
|---|---|---|
| (а) Негативный тест проведения (ожидаемый Отказ) | Упавшая вложенная транзакция отравляет внешнюю: «В данной транзакции уже происходили ошибки!» при последующих чтениях | .УдалениеТестовыхДанных() + .После("Очистка") |
| (б) Прод-код с гвардом ТранзакцияАктивна() | Двухфазные фиксации, реальные API-вызовы, регистры с уникальным ключом — падают или ведут себя непредсказуемо внутри транзакции | .После("Очистка") с ручной зачисткой |
| (в) Клиентский контекст | ДобавитьКлиентскийТест — транзакционный откат на клиенте недоступен по архитектуре платформы | Перед/После-обработчики с серверным контекстом |
Изоляция декларируется в коде (.ВТранзакции(), teardown), а доказывается фактом — счётчиками в БД до/после прогона. Зелёный прогон НЕ доказывает чистоту: тест может пройти и оставить мусор.
Перед-хендлеры и тела тестов (справочники, документы, регистры), включая объекты контекстных хелперов (СоздатьКонтекстТеста и т.п.).ВЫБРАТЬ КОЛИЧЕСТВО(*) ИЗ Справочник.X (платформенный запрос: MCP execute_query / консоль запросов). Для регистров сведений — счётчик по измерению-маркеру теста, не по всей таблице.ВЫБРАТЬ Наименование ... ГДЕ Наименование ПОДОБНО "...%"), определить создающий хендлер/тест, добавить teardown, повторить чеклист с шага 2.Ловушки (прецедент TASK-173 / TASK-165.7):
| Ловушка | Суть |
|---|---|
| Авто-удаление YaxUnit падает на .Записать(Ложь, Истина) | УстановитьПометкуУдаления повторно валидирует обязательные реквизиты и отказывает — объект остаётся. Teardown — физическое удаление: Объект.ОбменДанными.Загрузка = Истина; Объект.Удалить(); |
| Частичный teardown | После-хендлер чистит только часть созданного (например семафор-регистр, но не аккаунт-справочник) — выглядит как teardown, но мусорит |
| «GREEN = чисто» | 41/41 тестов зелёные, при этом один модуль оставлял +15 объектов за прогон — обнаружено только счётчиками |
| Антипаттерн | Правильно |
|-------------|-----------|
| Данные в ИсполняемыеСценарии | Данные в теле теста или Перед-обработчике |
| Один тест проверяет 10 условий | Один тест — одно утверждение |
| Тест зависит от порядка выполнения | Каждый тест изолирован |
| Хардкод ссылок на объекты ИБ | Создавать через ЮТест.Данные() |
| Тестировать приватную логику | Тестировать через публичный интерфейс |
| Мокировать тестируемый модуль | Мокировать только зависимости |
| Пишущий набор без .ВТранзакции() | .ВТранзакции() по умолчанию; исключения — с комментарием + teardown |
| Справочники.X.СоздатьЭлемент() в тесте | ЮТест.Данные().СоздатьЭлемент() + регистрация в коллекторе |
| .ВТранзакции() на негативном тесте проведения | Исключение (а) — отравляет транзакцию; использовать .УдалениеТестовыхДанных() + .После() |
| Создал объект, не зарегистрировал в коллекторе | Каждое создание → Коллектор.Зарегистрировать(Ссылка) сразу; teardown → Коллектор.ОчиститьВсё() |
| Teardown перебором базы по имени/префиксу/регекспу (ПОДОБНО "Тест%") | Хрупко (ложно-широкий снесёт боевое, ложно-узкий оставит хвост). Только разовый сметатель историч. мусора; штатный teardown — коллектор (точные ссылки) |
| «GREEN = чисто» — приёмка без счётчиков | Чеклист самоочистки: счётчики до/после запросами, два прогона, дельта 0 |
Оркестрация фаз и цикла Red→Green — правило tdd-policy (framework/rules/tdd-policy/SKILL.md). Там же живут (не дублируются здесь) правило при падении теста у Tester (test_error → Tester чинит сам; implementation_error → СТОП, возврат Developer) и требование User/Role context в Test Plan (для кода с SetPrivilegedMode / AccessRight / RoleAvailable спека обязана указать пользователя, режим и ожидаемый результат — иначе full-rights runner даёт false-positive). Ниже — только слои и границы ролей, специфичные для написания тестов.
Тесты и реализация пишутся разными агентами в разных фазах: автор тестов не знает реализацию, автор кода не модифицирует тесты. Phase 3a и 3b — параллельно; 3c стартует после завершения обоих.
| Слой | Фаза | Агент | Покрывает |
|------|------|-------|-----------|
| BDD (acceptance) | 3a | Scenario-Author | Поведение через UI (.feature) |
| TDD (unit, Red) | 3b | Developer-Tests | Публичные методы, MUST-сценарии, базовые негативы |
| TDD (green) | 3c | Developer-Code | Реализация, проходящая unit-тесты |
| Coverage | 4 | Tester | Edge cases, интеграция, регрессия, BDD + unit |
Границы агентов:
depends_on:
development
1C server maintenance webhooks: container restart and external component cache cleanup
development
Interactive DAP debugging of a single BSL procedure
tools
Rules for using RLM tools for project search and navigation in 1C/BSL
development
Creates web applications and routes on Winow (a web server on OneScript and Autumn). Use when working with a web server on OneScript, routing, or Winow controllers.