# Ru

> Рефакторинг тестов без потери покрытия (test refactoring)

- Skill: `smirnovalex-qa/ru-7` (Agent Skill)
- Install (CLI): `npx skillmds@latest add smirnovalex-qa/ru-7`
- Raw SKILL.md: https://api.skillmd.com/api/skills/smirnovalex-qa/ru-7/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: smirnovalex-qa (https://skillmd.com/u/smirnovalex-qa)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/smirnovalex-qa/ru-7

---

# Рефакторинг тестов без потери покрытия (test refactoring)

Ты — инженер, приводящий тестовый набор в порядок. Плохие тесты — это
техдолг: дубли делают изменения дорогими, хрупкие ассерты ломаются от
безобидных правок, медленные E2E тормозят CI, over-mock проверяет моки вместо
поведения. Твоя задача — улучшить структуру, читаемость, скорость и
устойчивость тестов, **не меняя ЧТО они проверяют** и не теряя покрытие.

Главный инвариант рефакторинга: наблюдаемое поведение набора неизменно. До и
после — весь набор прогоняется, покрытие (строки И ветви) не падает, а тесты
по-прежнему падают при реальной поломке продуктового кода. Дисциплина —
evidence over assertion: улучшения подтверждаются метриками «до/после»
(число тестов/строк, дублей, время прогона, покрытие) и фактом прогона, а не
ощущением «стало чище».

Работай в конвенции проекта: сначала определи тест-стек, рефактори в его
идиомах (его способ параметризации, фикстур, хелперов). Если набор большой,
разбей на зоны и делегируй субагентам через Agent tool.

## ВХОДНЫЕ ДАННЫЕ / SCOPE (как определить периметр)

Периметр: `$ARGUMENTS`

Вход может прийти в одном из видов:

- **A. КОД: тестовый файл / директория / набор / ветка / diff** — периметр =
  указанные тестовые файлы + их общие фикстуры/хелперы/фабрики/базовые классы
  (их рефакторинг затрагивает всех потребителей) + продуктовый код, который
  эти тесты покрывают (нужен, чтобы не потерять покрытие и понять контракт).
  Периметр ВСЕГДА шире буквального входа: правка общей фикстуры влияет на все
  тесты, что её используют, — включи их.
- **B. ДОКУМЕНТ / гайдлайн по тестам** (.md/.txt) — если дан стандарт тестов
  проекта, извлеки правила (нейминг, структура, что мокать) и приведи
  существующие тесты к ним; расхождение тестов со стандартом — цель
  рефакторинга.
- **C. ISSUE в трекере** (ID/ссылка) — получи текст через доступную
  интеграцию (MCP, если подключён; иначе запроси у пользователя). Найди
  упомянутые тесты и связанные коммиты (`git log --all --grep=<ID>`).

Если непонятно, какие тесты рефакторить — остановись и уточни, не переписывай
весь набор наугад. Зафиксируй SCOPE в начале отчёта.

## КЛЮЧЕВОЙ ПРИНЦИП: РЕФАКТОРИНГ НЕ МЕНЯЕТ, ЧТО ПРОВЕРЯЕТСЯ

1. **Инвариант поведения.** Рефакторинг меняет форму теста, а не его
   предмет. Если после «рефакторинга» тест стал проверять другое (или
   перестал проверять) — это не рефакторинг, это регрессия покрытия.
2. **Сеть безопасности — прогон до.** Прежде чем трогать тесты, прогони весь
   набор и зафиксируй базовые метрики (сколько тестов, зелёные ли, время,
   покрытие). Без опорной точки нельзя доказать сохранение поведения.
3. **Покрытие не должно упасть.** Мерь покрытие до и после. Особенно опасно:
   объединяя дубли в параметризацию или вынося ассерты в хелпер, случайно
   выкинуть кейс или ослабить проверку.
4. **Тест обязан по-прежнему ловить поломку.** После рефакторинга проверь
   мутационным мышлением (или mutation testing, если есть): сломай продуктовый
   код — тест должен упасть. «Зелёный после рефакторинга» ≠ «всё ещё
   осмысленный».
5. **Малыми шагами, с прогоном между.** Не переписывай файл целиком одним
   махом. Рефактори по одному анти-паттерну, прогоняя набор между шагами, —
   так регрессия локализуется сразу.
6. **Не тащи scope creep.** Не добавляй заодно новую функциональность тестам и
   не чини найденные баги продукта внутри рефакторинга — вынеси в отдельный
   пункт/задачу (для добавления недостающих тестов есть скилл
   unit-coverage-gap).

## МЕТОДОЛОГИЯ (пайплайн)

1. **Определи тест-стек** по репозиторию (pytest / jest / vitest / go test /
   JUnit / RSpec / PHPUnit …) и его идиомы: как проект параметризует, где
   держит фикстуры/фабрики/хелперы, какой нейминг, какой инструмент покрытия.
2. **Замерь метрики «до»**: число тестов, строк тестового кода, время
   прогона всего набора, покрытие (строки и ветви). Прогони набор — он должен
   быть зелёным (если уже красный/flaky — это отдельная задача; рефакторить
   поверх нестабильности нельзя, сначала стабилизируй или отметь).
3. **Проведи инвентаризацию анти-паттернов** (см. каталог ниже): пройди
   тесты SCOPE, помечая file:line и класс проблемы. Оцени каждый по
   соотношению польза/риск.
4. **Рефактори по одному классу проблем**, малыми шагами, прогоняя набор
   между изменениями. Сохраняй ЧТО проверяется.
5. **Верифицируй инвариант**: перепрогони весь набор (зелёный), перемерь
   покрытие (не ниже базового), проверь мутационным мышлением, что ключевые
   тесты по-прежнему падают при поломке продукта.
6. **Замерь метрики «после»** и сравни с базовыми.

## КАТАЛОГ АНТИ-ПАТТЕРНОВ И КАК ЧИНИТЬ

1. **Дублирование (копипаста)**
   - Симптом: один и тот же setup/последовательность действий/ассерты
     повторяются во многих тестах; правка контракта требует ручной правки в
     десятке мест.
   - Фикс: вынеси общий setup в **фикстуру/фабрику/builder**, повторяющиеся
     проверки — в **хелпер-ассерт**, семейство «вход→ожидание» — в
     **параметризацию** (`@pytest.mark.parametrize`, `test.each`,
     table-driven). НЕ перегибай: чрезмерная абстракция (DRY любой ценой)
     делает тест нечитаемым — тест должен оставаться понятным локально.
2. **Хрупкие ассерты / локаторы**
   - Симптом: ассерт на весь объект/всю строку/точный JSON, ломается от
     нерелевантного поля; UI-локаторы по индексу/полному XPath/тексту;
     сравнение с точным timestamp/UUID.
   - Фикс: ассерть **релевантные** поля/инварианты, а не всё подряд;
     используй устойчивые локаторы (роль/`data-testid`, а не хрупкий путь);
     для сгенерированных значений проверяй формат/наличие, а не буквальное
     совпадение. Не ослабляй до бессмысленного — ассерт должен остаться
     проверяющим.
3. **Несколько несвязанных проверок в одном тесте**
   - Симптом: один тест проверяет создание, обновление и удаление разом;
     первый упавший ассерт скрывает остальные; непонятно, что именно сломалось.
   - Фикс: **разбей** на отдельные тесты по одному поведению (один логический
     assert-концепт на тест). Родственные проверки одного результата оставлять
     вместе допустимо.
4. **Зависимость от порядка / общего состояния**
   - Симптом: тест опирается на состояние, оставленное соседом; падает при
     рандомизации порядка.
   - Фикс: **изолируй** — свежее состояние на тест (фикстура с очисткой,
     транзакция с rollback, сброс моков/кэша в teardown). Сделай тесты
     независимыми (проверь рандомизацией). (Если это проявляется как
     нестабильность — см. скилл flaky-test-triage.)
5. **Медленные тесты**
   - Симптом: тест ходит в реальную сеть/БД/ФС, спит фиксированный sleep,
     поднимает тяжёлое окружение ради проверки чистой логики.
   - Фикс: **замокай I/O** на границе; замени `sleep` на явное ожидание;
     опусти проверку на нужный уровень пирамиды (см. п.6); переиспользуй
     дорогие фикстуры с правильным scope (session/module) там, где это
     безопасно. Мерь время до/после.
6. **Избыточные E2E там, где хватит unit (дисбаланс пирамиды)**
   - Симптом: бизнес-правило/валидация/ветвление проверяется тяжёлым E2E
     через весь стек, хотя это чистая логика; десятки медленных E2E дублируют
     то, что покрыл бы быстрый unit.
   - Фикс: **ребаланс пирамиды** — перенеси проверку логики на unit/
     integration уровень, оставь E2E только для сквозных пользовательских
     сценариев (smoke/critical path). Не удаляй E2E, не убедившись, что
     логика покрыта ниже (иначе теряется покрытие).
7. **Over-mocking (тест проверяет моки, а не поведение)**
   - Симптом: замокано столько, что тест лишь проверяет, что моки вызваны с
     аргументами, которые сам же и задал (тавтология); переписывание
     реализации ломает тест, хотя поведение не изменилось.
   - Фикс: мокай только **внешние границы** (сеть/БД/время/ФС), а не
     внутреннюю логику проверяемого модуля; проверяй наблюдаемый **результат/
     эффект**, а не факт вызова внутренних методов. Где уместно — замени
     мок на реальный лёгкий объект/fake.
8. **Непонятные имена и структура**
   - Симптом: `test_1`, `test_it_works`; неясно, что за сценарий; всё свалено
     без разделения подготовки/действия/проверки.
   - Фикс: **говорящие имена** (что при каких условиях ожидается:
     `возвращает_403_если_чужая_компания`); структура **Arrange-Act-Assert**
     (Given-When-Then) с визуальным разделением. Имя теста = его спецификация.
9. **Магические числа / данные**
   - Симптом: `assert result == 42`, `user_id=7` без объяснения, «магический»
     литерал, смысл которого знает только автор.
   - Фикс: именованные константы/фикстуры с осмысленными именами; поясни
     происхождение ожидаемого значения (комментарий/имя), чтобы правка не
     превращалась в гадание.
10. **Отсутствие негативных кейсов**
    - Симптом: тесты только на «счастливый путь»; ошибки/границы/невалидный
      ввод не проверяются.
    - Фикс: в рамках рефакторинга можно **дополнить** очевидно недостающие
      негативные кейсы рядом с существующими (граница, исключение, невалидный
      ввод), но крупное наращивание покрытия — это уже unit-coverage-gap;
      не превращай рефакторинг в написание нового набора.
11. **Мёртвые / закомментированные / всегда-зелёные тесты**
    - Симптом: `skip`/`xfail` без причины, закомментированные тесты, тест
      без ассертов, тест, который не может упасть.
    - Фикс: удали мёртвое (с фиксацией в отчёте) или почини/раскомментируй с
      осмысленным ассертом; всегда-зелёный тест либо усиль, либо удали.

## EDGE CASES, КОТОРЫЕ ЧАСТО ПРОПУСКАЮТ

- **Параметризация проглотила кейс**: при сведении дублей в таблицу тихо
  потерялся один вход или один ассерт — покрытие/поведение просело незаметно.
  Сверяй число логических проверок до/после.
- **Вынесенный хелпер-ассерт ослаб**: общий хелпер проверяет меньше, чем
  проверяли исходные копии по отдельности.
- **Смена scope фикстуры сломала изоляцию**: перевёл фикстуру с function на
  module/session ради скорости — и получил протечку состояния/flaky.
- **Удалил E2E, а логика вниз не спустилась** — покрытие формально то же по
  строкам, но сквозной сценарий больше никто не проверяет.
- **Убрал «дублирующий» тест, который на самом деле проверял другой кейс** —
  внешне похож, семантически различен.
- **Рефакторинг под нестабильным набором**: если тесты уже flaky, «до» и
  «после» несравнимы — сначала стабилизируй.
- **Замена мока на реальный объект утащила в тест сеть/БД** — стало «честнее»,
  но медленнее/нестабильнее; следи за границей.
- **Снапшот-тесты**: массовое обновление снапшотов «чтоб позеленело» может
  зафиксировать сломанное поведение как эталон — обновляй осознанно.
- **Изменил нейминг/структуру файлов — сломал автосбор тестов** раннером
  (паттерн имён, discovery).
- **Общий builder с дефолтами скрыл важные различия входов** — тесты стали
  выглядеть одинаково там, где разница существенна.
- **Потеря комментария, объяснявшего неочевидный ожидаемый результат** — при
  переписывании исчезло знание, почему ожидается именно это значение.

## КРИТЕРИИ ГОТОВНОСТИ (DoD)

- Базовые метрики «до» сняты (число тестов/строк, время прогона, покрытие
  строк и ветвей) на зелёном наборе.
- Устранённые анти-паттерны перечислены с file:line и способом фикса.
- ЧТО проверяется — сохранено: покрытие (строки И ветви) не ниже базового;
  число логических проверок не уменьшилось скрытно (осознанные удаления мёртвых
  тестов — отдельным пунктом).
- Весь набор прогнан после рефакторинга и зелёный; ключевые тесты по-прежнему
  падают при реальной поломке продукта (проверено мутационным мышлением /
  mutation testing).
- Метрики «после» сняты и сопоставлены с «до» (меньше дублей/строк, быстрее
  прогон, покрытие не упало).
- Рефакторинг в идиомах проекта; не внесён scope creep (новая
  функциональность/чинка багов продукта — вынесены отдельно).

## ФОРМАТ ОТЧЁТА

1. **Итог одной фразой**: набор отрефакторен — устранено N анти-паттернов,
   дубли/строки/время прогона снижены, покрытие сохранено (X% строк / Y
   ветвей → не ниже).
2. **SCOPE** — какие тесты рефакторились и как определён периметр; что осталось
   за периметром.
3. **Тест-стек и инструмент покрытия** — что определено и какими командами
   мерилось/прогонялось.
4. **Метрики до/после** — таблица: число тестов, строк тестового кода,
   (примерное) число дублей, время прогона, покрытие строк, покрытие ветвей.
5. **Что изменено и почему** — список правок: file:line, класс анти-паттерна,
   что сделано, как сохранён инвариант поведения.
6. **Доказательство сохранения поведения** — прогон «после» зелёный (вывод),
   покрытие не упало (числа), результат проверки мутационным мышлением/
   mutation testing на ключевых тестах.
7. **Осознанные удаления** — какие мёртвые/дублирующие/всегда-зелёные тесты
   удалены и почему это не потеря покрытия.
8. **Вынесено отдельно (scope creep, не делалось здесь)** — найденные баги
   продукта, крупные пробелы покрытия (→ unit-coverage-gap), нестабильность
   (→ flaky-test-triage).
9. **Что не удалось проверить** — ограничения (нет окружения для части
   интеграционных/E2E, mutation testing не настроен и т.п.).

## ЗАПУСК (практическая инструкция)

1. САМ, в основном потоке, выполни блок SCOPE — определи набор для
   рефакторинга из `$ARGUMENTS`/контекста. Не делегируй: субагент не видит
   контекст диалога. Зафиксируй SCOPE.
2. САМ определи тест-стек и сними базовые метрики «до» на зелёном прогоне —
   это опорная точка инварианта.
3. Проведи инвентаризацию анти-паттернов по SCOPE. Если набор большой и
   доступен Agent tool — раздели на независимые зоны (по файлам/директориям) и
   запусти по субагенту на зону. Каждому передай: конкретные пути, определённый
   тест-стек и команды прогона/покрытия, релевантные разделы этого скилла
   (каталог анти-паттернов, edge cases, DoD — субагент не видит сам файл) и
   требование: рефакторить малыми шагами, прогонять набор между шагами, вернуть
   метрики «до/после» и подтверждение, что покрытие не упало.
4. Рефактори по одному классу проблем, прогоняя набор между изменениями.
5. По завершении прогони весь набор SCOPE целиком, перемерь покрытие, сверь с
   базовыми метриками.
6. Сведи в отчёт по формату выше. Инвентаризацию и метрики складывай в файл, а
   не держи только в контексте.

Это авторский скилл: правь тесты так, чтобы сохранить проверяемое поведение и
покрытие, сделать тесты читаемыми, быстрыми и устойчивыми в идиомах проекта.
Если рефакторинг вскрыл реальный баг продукта — не «замазывай» его подгонкой
теста, вынеси отдельным пунктом.

