# Миграция на Testo

Перенеси набор тестов этого проекта с PHPUnit (или Pest) на [Testo](https://github.com/php-testo/testo). Иди по шагам по порядку; первый шаг блокирующий.

**Рекомендуемый первый шаг: получить скилл миграции.** В поставке Testo есть `testo-migrate-from-phpunit` — самый проработанный путь для этой задачи: полная таблица соответствия конструкций, скрипты, которые находят остатки PHPUnit и разбивают работу на партии, шаблон переноса отдельного файла и контрольные точки на каждой фазе. Если скилл тебе уже доступен — загрузи его и следуй ему. Если нет — сначала настрой синхронизацию скиллов: `composer require --dev llm/skills` и одна команда `composer skills:init`, по инструкции <https://php-testo.github.io/ru/docs/ai/prompts/skills.md>, — и затем загрузи его.

Шаги ниже — тот же план в сжатом виде, для проекта, где скиллы установить нельзя.

## 0. Сначала прочитай документацию

Прежде чем что-либо переписывать, загрузи `https://php-testo.github.io/llms.txt`, а если ответа там нет — `https://php-testo.github.io/llms-full.txt`. Testo **не** совместим с PHPUnit на уровне исходников: порядок аргументов в ассертах перевёрнут, обнаружение тестов построено на атрибутах, базового класса нет. Сверяй каждую конструкцию с документацией.

## 1. Точка отката (блокирующий шаг)

Миграция тестов — ломающее изменение, и какое-то время сьют может быть красным.

1. Выполни `git status --short`. Если дерево грязное — остановись и попроси закоммитить или спрятать изменения: откат не должен затронуть чужую несвязанную работу.
2. Создай отдельную ветку: `git checkout -b migrate-to-testo`.
3. Прямо проговори план отката и дождись подтверждения, прежде чем трогать хоть один тест.

Если проект не под контролем версий — скажи, что миграция без точки отката небезопасна, и попроси инициализировать git (или сделать резервную копию), прежде чем продолжать.

## 2. Выбери объём

Мигрируй по одному срезу за раз — обычно начинают с `tests/Unit`: чистые тесты с малым количеством моков переносятся легко. Если объём неочевиден, спроси, и дальше неси выбранные каталоги через все последующие шаги.

Никогда не запускай PHPUnit и Testo на одних и тех же тестах в CI: срез в каждый момент принадлежит ровно одному раннеру.

## 3. Установка

Testo ставится и настраивается здесь так же, как в любом другом проекте, поэтому не выдумывай свой порядок: пройди промпт инициализации — <https://php-testo.github.io/ru/docs/ai/prompts/init.md>. Он ставит `testo/testo`, генерирует `testo.php` командой `vendor/bin/testo init` и добавляет джобу в CI. Если Testo в проекте уже настроен, пропусти этот шаг.

Для миграции нужны ещё два пакета — сам Rector и набор правил, который конвертирует PHPUnit в Testo:

```bash
composer require --dev testo/bridge-rector rector/rector
```

Прежде чем идти дальше, сверь сгенерированный `testo.php` с реальной структурой проекта: Rector переносит тесты именно в те каталоги, которые объявлены в конфиге.

CI-джоба из того промпта запускает Testo по всему проекту — пока миграция не закончена, ограничь её выбранным срезом (`--suite` или `--path`), чтобы те же тесты не гонялись ещё и под PHPUnit.

## 4. Механическая конвертация через Rector

Направь `rector.php` на нужные каталоги и подключи набор правил:

```php
// rector.php
use Rector\Config\RectorConfig;
use Testo\Bridge\Rector\Set\TestoRectorSetList;

return RectorConfig::configure()
    ->withPaths([__DIR__ . '/tests/Unit'])
    ->withSets([TestoRectorSetList::PHPUNIT_TO_TESTO]);
```

```bash
vendor/bin/rector process --dry-run   # сначала посмотри, что получится
vendor/bin/rector process
```

Перед коммитом просмотри результат обычным `git diff`.

Для Pest мост конвертирует только цепочки `expect()->toX()` (`TestoRectorSetList::PEST_TO_TESTO`) — замыкания `test()`/`it()` придётся перестраивать в классы вручную. Считай Pest преимущественно ручным переносом, которому этот набор лишь помогает.

## 5. Доведи структуру руками

Rector делает механическую основную массу, но не всю миграцию. Что остаётся по каждому файлу:

- Убери `extends PHPUnit\Framework\TestCase` и импорты PHPUnit — базовый класс Testo не нужен.
- Сделай обнаружение явным через `#[Test]`: атрибут на классе, если тестами являются все публичные методы, и на отдельных методах — иначе.
- Замени то, что набор правил не тронул: моки PHPUnit, кастомные констрейнты и прочие конструкции без точного аналога. Они намеренно оставлены на месте, а не выброшены, — пройдись по ним поиском.
- Перенеси хуки жизненного цикла на `#[BeforeTest]`, `#[AfterTest]`, `#[BeforeClass]`, `#[AfterClass]`.

Сохраняй смысл ассертов один в один и не придумывай дополнительных сценариев — это перенос, а не переписывание.

## 6. Проверка

```bash
vendor/bin/testo --json
```

Работай по JSON-отчёту, сужая прогон через `--filter`, `--path` или `--suite`, пока чинишь, и в конце прогони всё целиком. Коды выхода: `0` — всё прошло, `1` — есть падения, `2` — некорректная команда или конфигурация.

Сравни количество тестов со старым прогоном PHPUnit: миграция, тихо потерявшая часть тестов, выглядит зелёной.

## 7. Уборка

Когда срез стал зелёным на Testo, убери эти каталоги из `phpunit.xml` и из CI-джобы, а на их место поставь запуск Testo. Выбрасывай `phpunit/phpunit` из `composer.json` только тогда, когда на нём ничего не осталось.

## 8. Отчёт

Подведи итог: какие каталоги мигрированы, сколько тестов выполнялось до и после, что сделал Rector, а что ты переносил руками, что не удалось сконвертировать точно и что до сих пор работает на PHPUnit.
