Skip to content

[Testing] Roadmap автоматического покрытия: PHPStan в CI, PHPUnit, интеграционные тесты #429

Description

@biz87

Описание

Roadmap по автоматическому покрытию MiniShop3. Дополняет уже открытый #395 (PHPUnit-каркас + первые unit-тесты на чистые функции) — здесь общая картина: что покрываем, в каком порядке, с приоритетом по риску.

Контекст. После #394 (CI-гейт: php -l + smoke + ESLint) на PR теперь есть автопроверка, включён branch protection на beta. Но текущее покрытие — 5 standalone smoke-скриптов на чистые функции (charset-нормализация, URL email-verify, CSV option parse, direct-filter-контракт). Реальные баги последних 2 месяцев (repeater save-drop, inline-edit type mismatch, null payload semantics) сидели в stateful-логике с БД, которую smoke-тесты в принципе не достают.

Уровень 0 — PHPStan в CI (highest priority, дешёвый)

PHPStan уже настроен в проекте (phpstan.neon + phpstan-baseline.neon, level 5). Мы гоняем его вручную на каждом ревью — это главный ловец регрессий типов/сигнатур. Он не в CI-гейте из #394 (автор явно оставил follow-up).

  • Добавить job phpstan в .github/workflows/ci.yml рядом с PHP lint + smoke.
  • composer stan скрипт → phpstan analyse -c phpstan.neon --memory-limit=1G.
  • Baseline остаётся — падаем только на новых ошибках, не на исторических 168.
  • После стабилизации — добавить в required checks branch protection.

Почему первым: нулевая инфраструктурная стоимость (всё уже настроено), максимальный ROI — ловит именно тот класс ошибок, что мы вылавливаем руками.

Уровень 1 — PHPUnit каркас + чистые функции (см. #395)

Отслеживается в #395. Кратко: подключить PHPUnit 11, перенести 5 существующих smoke в нормальные тест-кейсы с автодискавери и человеческими assertions. Smoke-раннер остаётся для CI-совместимости, пока не мигрируем полностью.

Кандидаты (чистые функции, без MODX):

Статус: закрыт через #395 / #433; RepeaterFieldService#493.

Уровень 2 — интеграционные тесты stateful-логики (главная ценность, дорого)

Здесь живут реальные баги. Требует тестовой БД (SQLite in-memory или MySQL service в CI) + bootstrap MODX-lite или мок xPDO. Это большой инфраструктурный трек — отдельные PR по одному домену.

Приоритет по «частоте багов за последние релизы»:

  1. Options sync (OptionService / OptionSyncService / ProductDataService::saveOptions) — При копировании товара через «Дублировать ресурс» теряются значения опций #257, [Bug] Не удаляется опция из товара #199, feat(extra-fields): repeater field type (ms3-repeater) #301, fix(import): split multi-value option columns in CSV import #312 все крутились тут. Save/removeOther-семантика, multi-value, repeater exclusion.
  2. Order flow (OrderService, OrderDraftManager, OrderFinalizeService, clampComputedTotal) — [Feature] Отрицательная стоимость доставки и оплаты #211, [Feature] Пересчет стоимости заказа при смене способа доставки в админке #212, Защита total от ухода в минус + UX-маркер для скидок в настройках доставки/оплаты #265, fix(order): multiply draft weight by product count #403. Расчёт стоимости, веса, negative-clamp, draft → final.
  3. Product data payload (ProductDataPayloadTrait, updateProductData whitelist) — [Feature] кастомные поля msProductData в процессор create product #297, fix(product): сохранение Data и extra fields в процессорах Create/Update #298, feat(extra-fields): repeater field type (ms3-repeater) #301, fix(api): per-field null payload semantics in Manager API (#289) #310. Null-семантика, extra-field whitelist, silent-drop регрессии.
  4. Customer auth / token (AuthManager, TokenService) — [Bug] Не работает авторизация покупателя #285, fix(manager): prevent search autofill after customer edit (#286) #319, fix(customer): stop checkout token takeover via email #391, fix(customer): reject expired API tokens instead of silent renew #396, fix(api): omit customer secrets from profile/add responses #428. Session/token lifecycle, expired-token, takeover-защита.
  5. Cart (Cart facade + services) — базовый флоу add/change/remove, пока почти без покрытия.

Статус: starter + follow-up в #493 (SQLite Fake xPDO / draft store; не полный MySQL service в CI):

  1. Options sync — OptionSyncServiceTest
  2. Order flow — clamp / draft cost / OrderFinalizeServiceTest
  3. Product payload — trait + UpdateProductDataPermissionsTest
  4. Auth/token — ResolveApiTokenTest
  5. Cart — pure helpers + CartFacadeDraftStoreTest

Уровень 3 — Vue/JS (низкий приоритет сейчас)

ESLint уже в CI. Юнит-тесты компонентов (Vitest) — только если появится регрессионная боль во фронте. Composables из #397 (useGridFilterParams, useCategoryProductsInlineEdit) — хорошие кандидаты, они чистые.

Статус: Vitest + happy-dom в #493 (useGridFilterParams, useCategoryProductsInlineEdit, formatLocalDateYmd); CI step в job vueManager lint.

Метрика готовности к stable

Связано с внутренним критерием перехода beta → stable. «Install reliability» и «строгий patch-контракт» частично защищаются уровнями 0-1; но уверенность в отсутствии data-loss даёт только уровень 2. Без интеграционного покрытия горячих путей мы каждый релиз полагаемся на ручное прокликивание на dev.

Что предлагаю по порядку

  1. Сейчас: уровень 0 (PHPStan в CI) — один PR, дёшево, огромный ROI.
  2. Далее: уровень 1 ([Feature] PHPUnit: каркас + 2–3 unit-теста на чистые функции #395) — каркас + перенос smoke + добавить security-критичные чистые функции (OptionColumnSpec, GridEditorReferenceRegistry).
  3. Затем итеративно: уровень 2 по одному домену, начиная с options sync и order flow.
  4. Уровень 3 — по потребности.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestpriority: highВажно исправить в ближайшее времяtech-debtMaintainability / refactor / architecture debt

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions