unica-testing · diff
git:20261025.6e55549 to git:20261026.5b69f63
2 added, 1 removed. Audit A to A.
---
name: unica-testing
description: Выбор, написание, запуск и ревью тестов самого Unica — граница проверки, размеры small/medium/large, Rust nextest, Python unittest и разбор падений. Используй при изменении проверок или проверяемого поведения; тесты прикладного кода 1С относятся к unica:test-authoring.
---
# Тестирование Unica
Сначала установи, какое нарушение должен обнаружить тест. Найди ближайшие
проверки и применимые правила в `arch/rules/` по предмету, исходнику и `check`;
прочитай их до выбора решения. Отсутствие записи не отменяет
гарантии продукта. Все команды ниже выполняются от корня репозитория.
## Что дочитывать
Открывай справку, когда работа касается её предмета:
| Работа | Что открыть |
| --- | --- |
| `.github/workflows/`, отбор, итоговый шлюз CI, отчёты Allure, сайт и ночной ярус в `scripts/ci/`, сборка, передача и публикация артефактов | [Конвейер и отчёты](references/ci-pipeline.md) |
| Выпускаемый XML 1С, фикстуры БСП schema-v2, корпус и верификаторы 8.3.27 | [Проверка XML платформой](references/platform-xml.md) |
## Что доказывать
Хороший тест различает допустимое поведение и конкретное нарушение контракта.
Назови такой контрпример: неверный результат, чужие данные, потерянное изменение,
запрещённая зависимость. Если проверка остаётся зелёной при этом нарушении,
она не доказывает нужную гарантию.
Для дефекта сначала добавь или уточни воспроизведение на текущем коде, запусти
его и убедись, что падение вызвано дефектом; затем исправляй код. Для новой
возможности выведи ожидаемый результат из согласованного поведения. Существующий
тест часто достаточно расширить: ссылка из архитектурного правила не требует
его отдельной копии. Если автоматическое воспроизведение недоступно, объясни
ограничение и содержательную ручную проверку; текстовую ошибку не закрепляй
тестом на наличие исправленной фразы.
Не создавай тест, который только повторно вызывает уже зарегистрированные
проверки ради одной ссылки. Общие вспомогательные функции и самостоятельные
параметризованные проверки допустимы. Привязку проверок к правилу задаёт
[unica-rules](../unica-rules/SKILL.md#проверки-и-разрывы).
Проверяй результат и существенные побочные эффекты. Выбирай случаи отказа,
отмены, восстановления и конкуренции по риску изменения. Не подменяй проверяемую
гарантию mock-ответом и не вычисляй ожидаемый результат копией реализации.
Проверка границы кода может читать AST, зависимости, схему или манифест:
нужны примеры разрешённой и запрещённой конструкции, включая нарушение
под другим именем, если имя не является частью контракта.
При изменении данных для другого инструмента проверь пример с новой
особенностью после чтения штатным потребителем или совместимым парсером.
Наличие нужных строк до разбора само по себе не доказывает сохранность
данных после чтения.
Не добавляй тесты на фразы, заголовки, длину AGENTS, скиллов или архитектурных
записей; на наличие отчёта о прошлой ручной проверке; на служебный реестр
ради самого реестра. XML-фикстура, ответ MCP и конфигурация, которую читает
программа, могут быть полноценными входами теста. Отличай их от прозы,
наличие которой не доказывает поведение.
## Граница и место
- Логику проверяй рядом с Rust-модулем, взаимодействие — через реальные
интерфейсы компонентов. Для запуска продукта и протокола выбирай подходящую
интеграционную цель в `crates/<crate>/tests/`.
- Python-проверки CI и упаковки находятся в `tests/ci/`, инструментов
разработчика — в `tests/dev/`. Архитектурное назначение теста не требует
отдельного каталога.
- Изолируй временные файлы, процессы и состояние; освобождай ресурсы и при
падении. Для unit-тестов `unica-coder` проверь существующие помощники в
`crates/unica-workspace/src/test_support/shared.rs`: снимок дерева, идентичность пути,
восстановление текущего каталога. Управляй временем и случайностью там,
где они влияют на результат. Повтор, произвольный sleep или `ignore`
не устраняет причину нестабильности.
- Тест не выходит молча, если хосту не хватает возможности: так он
записывается как PASS. В `tests/platform/` и фасадах `src/platform/`
возможность самой ОС помечай
`#[cfg_attr(not(<ОС>), ignore = "host capability <x>: <почему>")]`.
В остальных случаях — возможность ОС вне этих путей или зависящая
от тома и прав — вызови
`unica_process::test_support::skip_without_host_capability`, затем выйди.
Механизм и контуры описаны в
[ci-pipeline.md](references/ci-pipeline.md#возможности-хоста).
Атрибуты `cfg` и `cfg_attr` с `receipt-ledger-test-support` ставь на целые
Rust items: например, функцию, модуль или `impl`. Не ставь их на отдельные
выражения, операторы, поля или ветви `match`; `not` с этой feature запрещён
и на целых items. Проверяй границу командой
`python3.12 scripts/ci/check-receipt-ledger-test-support-boundary.py`.
Этот страж проверяет размещение атрибутов, а не полную эквивалентность
обычной и тестовой сборки.
В сценариях ReceiptLedger переходы проверяемой попытки выполняет продуктивный
код. Тест получает ответ по протоколу и наблюдает состояние. Подготовка
начального состояния, внесение отказа и действия второго владельца допустимы
как явно названные фикстуры; они не должны подменять работу проверяемой попытки.
При изменении этой обвязки запускай
`scripts/ci/check-receipt-harness-boundary.py`. Страж ограничивает доступ
к переходам; он не доказывает поведение сценария.
## Размер и разметка
Размер описывает стоимость и зависимости запуска. Модульный, интеграционный,
контрактный — назначение и граница теста; это другая классификация.
| Размер | Выбор | Разметка в Unica |
| --- | --- | --- |
| `small` | Локальная проверка с малыми затратами, без запуска продукта и сетевого взаимодействия | Умолчание после исключения старших размеров |
| `medium` | Процесс продукта, сокет, интеграционное окружение или замеренная длительность от 3 с | Rust: все Cargo integration targets (`kind(test)`), тесты файлов с признаками процесса/сокета и точные имена с замером в `MEASURED_MEDIUM_CASES` генератора. Python: `[medium].entries` в `.config/python-sizes.toml` |
| `large` | Нагрузка, ёмкость или длительный сценарий с существенными затратами | Явные Rust-фильтры в `.config/nextest.toml`; Python-переопределения пока не поддерживаются |
Отдельных size-аннотаций в тестах нет. Rust-классификатор
`scripts/ci/size-filters.py` смотрит на весь исходный файл: даже чистый тест
в файле с `std::process`, `Command::new`, сокетом или `CARGO_BIN_EXE_` может
стать `medium`. Это приближение; вызов через helper может скрыть зависимость.
Не назначай размер только по имени теста и не обходи отбор ради зелёного PR.
После изменения состава таких Rust-тестов обнови генерируемые блоки командой
`python3.12 scripts/ci/size-filters.py --write`. Она вызывает `cargo nextest list`
и может компилировать тесты; запуск без `--write` тоже обращается к Cargo.
Фильтры `large` генератор не меняет: согласуй отбор `queue`, `main`, `large`
и его timeout в `.config/nextest.toml`. Этот ярус выбирает точные имена
тестов в `daemon_receipt_ledger` и `unica_coder`; страж сверяет их с
атрибутами тестов и модулями исходников. Расширение на другие цели требует
изменения стража и проверки фактического отбора nextest.
Временный каталог `nightly-only` в том же файле — не размер. Он называет
тесты `medium`, которые очередь и `main` пока исключают, а `nightly-full`
и релиз исполняют: так тест ждёт зелёного Windows в полном nightly, не
скрываясь за `#[ignore]`. Страж требует точные имена существующих тестов
без `ignore`. Когда nightly подтвердит тест на всех его ОС, убери тест
из каталога, и он вернётся в очередь. Цель без оставшихся тестов убери
из `NIGHTLY_ONLY_TARGETS` в `tests/ci/test_gate_profiles.py`. Пустой
каталог снимается целиком: профиль `nightly-only`, его исключение
в `queue` и `main` и проверки стража. Профиль `queue` исключает тесты
каталога и при точном `-E`, поэтому изменённый тест каталога перед push
запускай его профилем, например
`cargo nextest run -p unica-coder --test platform_project_health --profile nightly-only -E 'test(=project_health_full_portable_repository_is_ready)'`.
При изменении хранилища квитанций сохраняй нагрузочный критерий
[#975](https://github.com/IngvarConsulting/unica/issues/975): за 60 секунд
не менее 1920 полных циклов резервирования, малого конечного ответа и ACK
(32 в секунду), p99 не более 250 мс, без ошибок вместимости и хранилища;
очередь записи освобождается за 2 секунды. Проверяй на поддерживаемых ОС
на воспроизводимом стенде с параметрами по требованиям #975. Пропуск
неподдерживаемого стенда не является прохождением; модель с искусственными
часами не доказывает реальную пропускную способность.
Отдельно проверь 32 одновременные аутентифицированные отмены: каждая
завершается не позднее 125 мс на Windows, macOS и Linux на воспроизводимом стенде.
Пакет отмен внутри актора не доказывает этот сетевой сценарий.
В Python укажи модуль, класс или метод: `test_module.Class.test_case`.
Манифест использует имена discovery без префикса `tests.ci` или `tests.dev`.
Страж замечает прямой вызов Cargo или импорт socket, но не все косвенные
зависимости. Одной секции `[large]` в TOML недостаточно: её runner не читает.
Если нужен Python `large`, сначала реши поддержку отбора в runner.
После изменения разметки запусти применимые `tests/ci/test_size_guard.py`,
`tests/ci/test_python_sizes.py` и `tests/ci/test_gate_profiles.py`.
## Что запускать
Начни с точного теста, затем проверь затронутое поведение и необходимые ворота.
Расширяй или повторяй запуск после изменений, сбоя, новой неопределённости
или требования CI. Успешный достаточный прогон не требует повторного большого
запуска ради уверенности. Исследования платформы за feature `research`
и команды `scripts/research/` выбирай только для соответствующей задачи.
`scripts/ci/run-tests.py` выбирает наборы целиком. `--profile pr` допускает
`small`, `queue`/`main` — `small` и `medium`, `release`/`all`/`large` — все размеры.
`--ecosystem rust|python|all` сужает экосистему; `--suite tests/ci|tests/dev`
и `--only-size` относятся только к Python. `--dry-run` лишь печатает команды.
Ночной профиль ворот `large` выбирает nextest `nightly-full`, полный Rust-набор
и оба Python suites, на Windows тоже — без исключений по платформе.
Nextest-профиль `large` остаётся каталогом
тяжёлых тестов для классификатора размеров; это другое назначение.
В CI `--phase build` создаёт архивы и полные планы всех полос раннера
(`--prepared` — каталог архивов джобы, `--results` — каталог отчёта);
`--phase run` исполняет их в той же джобе без Cargo-компиляции. Состав полос
по профилю и ОС задаёт `ci_rust_lanes.lanes`. `--phase verify` сверяет
объединение результатов с полным планом по ревизии, ОС, конфигурации
и попытке workflow. Для локальной точечной проверки сохраняй обычный
nextest-вызов. Исключение — тесты на подготовленный бинарь `unica`
из ordinary-архива (возможность `archive-fixture-binary`): вне
`run-tests.py --rust-lane ordinary` (фазы `run` и `medium` по архиву
из `build`) они пропускаются с этой причиной. Раскладку джоб, подготовленный ordinary binary для Python
и теневой selector описывает справка
в [конвейере](references/ci-pipeline.md).
При изменении навыков или поверхности, охваченных `tests/agent_evaluation`,
запускай [приёмку агентом Codex](../../../CONTRIBUTING.md#приёмка-агентом-codex).
Набор выполняй только локально. Отсутствие CLI или авторизации не заменяется skip. Протоколы
и привязанный к их hash вердикт сохраняй в `.session-temp/acceptance-agent/`.
Успешный source-корпус не заменяет этот прогон.
CI выбирает профиль по событию: pull request исполняет `pr`, очередь слияния
в `main` — `queue`, push в релизную ветку — `main`; соответствие задаёт
`GATE_PROFILE` в `.github/workflows/unica-plugin-release.yml`. Поэтому
`medium` впервые исполняется в очереди, а у релизной линии без очереди —
после push в неё. Зелёный PR не доказывает зелёную очередь. Rust-тесты
- на Windows идут только в ярусе `large`. Для разрешённой прозы PR и очередь
+ на Windows идут только в ярусе `large`; Clippy под Windows исполняет очередь
+ и кнопка `main` отдельной джобой. Для разрешённой прозы PR и очередь
могут пропустить Rust-тесты и Clippy; смешанная или неизвестная группа очереди
сохраняет полный контур. Push в `main` повторно их не исполняет, а при изменении
CI/toolchain или неизвестном diff только прогревает зависимости. Границы отбора
и независимость линта от тестов описаны в [конвейере](references/ci-pipeline.md).
Для применения метки `ci:full` следуй
[порядку ворот pull request](../../../CONTRIBUTING.md#ворота-pull-request).
Перед push запусти `medium`-проверки, которые исполняют изменённое поведение.
В Rust это тесты затронутого модуля и цели `crates/<crate>/tests/`; запускай
их с профилем `queue`, он повторяет отбор очереди. Например, правку демона,
каталога инструментов или словаря `run` проверяет фильтр
`-E 'test(/^infrastructure::daemon::server::/)'`: профиль `pr` исключает весь
этот модуль. Правку словаря `can` проверяет
`-E 'test(/^infrastructure::daemon::v13_can_honesty_tests::/)'`: его
длительные проверки идут только с очереди. В Python прочитай короткий список `[medium]` в
`.config/python-sizes.toml` и выбери записи по предмету правки: например,
изменённое описание в `tool_catalog.rs` проверяет
`tests.ci.test_tool_surface_ledger`.
Правку Rust может остановить Python-страж: например, храповик отказов считает
места по всему дереву, и поиск по имени изменённого файла его не находит.
Перед push запусти малую полосу `tests/ci`, которую исполняют ворота PR:
`python3.12 scripts/ci/run-tests.py --profile pr --ecosystem python --suite tests/ci`.
Наборы Python устойчивы к нагрузке CPU и могут идти параллельно со сборкой,
clippy и тестами Rust. Тест, чей исход зависит от скорости машины, — дефект.
Готовность жди по событию, а не сном; бюджет старта процесса и наблюдения
отделяй от проверяемого срока; где тест завершается событием, срок только
предохраняет от зависания. Проверяемый срок сохраняй, а фейк в таком тесте
заведомо не отвечает. Границу длительности выражай через контракт, например
«меньше времени жизни держателя», а не через запас на скорость раннера.
Для точечной проверки используй nextest или unittest напрямую. Например:
```sh
cargo nextest run -p unica-coder --lib --profile default -E 'test(/^infrastructure::workspace::tests::git_worktree_boundary_prevents_parent_workspace_discovery$/)'
python3.12 -m unittest tests.ci.test_rust_platform_boundary.RustPlatformBoundaryTests.test_repository_currently_complies_with_platform_boundary
```
Подставляй фактический crate, target и полное имя теста. `default` избегает
исключения нужного `medium`-теста фильтром `pr`. Python требует 3.12 и
зависимостей из `tests/ci/requirements.txt`. Для отчёта набора используй
`--results <каталог>`; подробности — в
[CONTRIBUTING.md](../../../CONTRIBUTING.md#отчёт-allure-локально).
`all` не означает все features и ignored-тесты. Для условной цели проверь
`required-features` в Cargo.toml. Штатный runner отдельно запускает
`daemon_receipt_ledger` с `receipt-ledger-test-support`, когда профиль допускает
`medium` или `large`. При точечном запуске этой цели нужны
`-p unica-coder --features receipt-ledger-test-support --test daemon_receipt_ledger`;
не включай эту feature всему workspace: она меняет путь fail-stop.
Ignored-тест запускай осознанно через `--run-ignored only` с точным фильтром
и выполненными предусловиями из причины пропуска.
Убедись по выводу, что нужный тест действительно исполнился. Ноль тестов,
skip, list и dry-run не доказывают поведение. При неясном отборе проверь
`cargo nextest list` с теми же target, features, профилем и фильтром; команда
может компилировать. У nextest настроен повтор: зелёный итог после падения
первой попытки не закрывает вопрос нестабильности.
## После падения и при ревью
Различай нарушение assertion, сбой сборки/окружения и отсутствие исполнения.
Из свежего вывода или отчёта получи полное имя и traceback; target, профиль
и features сверь с фактической командой. Allure хранит Rust `fullName` как
`binary::module::test`, Python — как `module.Class.test`; features в нём
не записываются. По имени найди исходный файл и прочитай тело теста вместе
с вызываемым кодом.
Падение по сроку не списывай на нагрузку без свидетельства: проверь `uptime`
и свои фоновые процессы, затем чередуй прогоны изменённого дерева и базовой
ревизии.
Затем сверь связанное обязательство и разбери расхождение по
[unica-rules](../unica-rules/SKILL.md#расхождение-с-кодом-или-тестом).
При ревью проверь, что тест исполняет обещанный сценарий, обнаруживает его
нарушение и не расширяет доказанную гарантию. Проверенный набор и существенные
ограничения конкретного прогона — пропуски, features, ОС, неустранённые
падения — указывай в отчёте о работе или разделе проверки PR.