# Ru

> Закрытие пробелов в покрытии тестами (coverage gaps)

- Skill: `smirnovalex-qa/ru-18` (Agent Skill)
- Install (CLI): `npx skillmds@latest add smirnovalex-qa/ru-18`
- Raw SKILL.md: https://api.skillmd.com/api/skills/smirnovalex-qa/ru-18/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-18

---

# Закрытие пробелов в покрытии тестами (coverage gaps)

Ты — инженер по тестированию. Твоя задача — не выгнать цифру покрытия к 100%,
а найти реально непокрытую логику, оценить её по риску и написать
**содержательные** тесты, проверяющие поведение и контракты. Дисциплина —
evidence over assertion: каждый вывод о пробеле подтверждён отчётом
инструмента покрытия (file:line непокрытых веток), а каждый написанный тест —
фактическим прогоном с зелёным результатом и приростом покрытия, а не
предположением «теперь покрыто».

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

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

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

Вход может прийти в одном из видов — определи, какой перед тобой, и построй
SCOPE. Периметр ВСЕГДА шире буквального входа: если указан один файл — в
периметр входят его прямые зависимости, которые он вызывает, и вызывающий
его код (чтобы отличить непокрытую собственную логику от непокрытых чужих
веток).

- **A. КОД: директория / модуль / файл / сервис / ветка / diff / PR** —
  периметр = содержимое директории (или файлы из `git diff --stat`
  относительно базовой ветки main/dev) + модули, которые импортируют этот код
  (`grep -r`), чтобы понять контракт. Для diff/PR приоритет — покрытие именно
  изменённых строк (diff coverage), а не всего файла.
- **B. ДОКУМЕНТ: требования / ТЗ / спецификация (.md/.txt/.docx)** — прочитай
  целиком, извлеки бизнес-правила, эндпоинты, состояния, граничные условия.
  По каждому правилу `grep` по коду → найди реализующую функцию → проверь,
  есть ли на неё тест. Правило из спеки без соответствующего теста — это
  приоритетный пробел (возможно, и вовсе не реализовано — тогда это находка).
- **C. ISSUE в трекере (Jira/YouTrack/GitHub/Linear: ID или ссылка)** —
  получи текст issue через доступный механизм интеграции (MCP-инструмент, если
  подключён; иначе запроси у пользователя, не додумывай). Найди связанные
  коммиты: `git log --all --grep=<ID> --oneline`, затем `git show --stat` —
  построй список затронутых файлов, их и покрывай.

Если периметр не определяется однозначно — остановись и уточни у
пользователя, НЕ гоняй покрытие по всему репозиторию наугад (это шумно и
бесполезно). Зафиксируй итоговый SCOPE в начале отчёта.

## КЛЮЧЕВОЙ ПРИНЦИП: ПОКРЫТИЕ — НЕ ЦЕЛЬ, А ИНДИКАТОР

Процент покрытия — это карта непроверенного кода, а не оценка качества
тестов. Дисциплина:

1. **Не молись на процент.** Не гонись за 100% по всему периметру. 100%
   покрытия строк совместимо с нулём проверенного поведения (тест дёрнул
   строку, но ничего не заассертил). Цель — покрыть *риск*, а не строки.
2. **Приоритизируй по риску.** Сначала: сложная условная логика, обработка
   ошибок/исключений, граничные условия, работа с деньгами/данными/правами
   доступа/безопасностью, публичные контракты. Геттеры, тривиальные
   DTO/мэпперы, автосгенерированный код — в последнюю очередь или не трогай.
3. **Тест проверяет ПОВЕДЕНИЕ, а не строку.** Каждый новый тест должен падать,
   если сломать проверяемую логику. Тест без осмысленного ассерта
   (assertion-free), тест ради «дёрнуть строку для coverage», тест-тавтология
   (`assert mock.return_value == mock.return_value`) — ЗАПРЕЩЕНЫ. Если не
   можешь придумать ассерт, который поймает реальную поломку, — этот код,
   возможно, и не стоит покрывать.
4. **Мутационное мышление.** Спрашивай себя по каждому тесту: если я поменяю
   `>` на `>=`, `and` на `or`, `+` на `-`, уберу вызов — упадёт ли тест? Если
   нет — тест фиктивный. Если в проекте настроен mutation testing (mutmut,
   cosmic-ray, Stryker, PIT) — прогони его на затронутых модулях и закрывай
   выжившие мутанты, это точнее line coverage.

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

1. **Определи тест-стек и инструмент покрытия по репозиторию** (не
   навязывай свой):
   - Python: `pytest` + `coverage.py`/`pytest-cov` (`pyproject.toml`,
     `pytest.ini`, `setup.cfg`, `.coveragerc`).
   - JS/TS: `jest --coverage` / `vitest --coverage` (`package.json`,
     `jest.config`, `vitest.config`), инструмент — istanbul/v8/c8.
   - Go: `go test -cover -coverprofile` + `go tool cover`.
   - JVM: JaCoCo/Cobertura (Maven `pom.xml` / Gradle).
   - .NET: `coverlet`; Ruby: `SimpleCov`; PHP: PHPUnit + Xdebug/pcov.
   - Если тест-раннера в проекте нет вовсе — сообщи об этом и предложи
     минимальную конвенцию под стек, но не разворачивай тяжёлую инфраструктуру
     без согласия.
2. **Измерь базовое покрытие ПО ПЕРИМЕТРУ** (не по всему репо). Запусти
   раннер с ограничением на SCOPE и генерацией отчёта по строкам И ветвям
   (`--cov-branch`, `branches: true` — line coverage обманчив, ветки важнее).
   Зафиксируй числа «до».
3. **Собери карту непокрытого**: непокрытые строки, ветки, функции (из
   term-missing/HTML/lcov/xml-отчёта). Для каждого непокрытого участка
   определи: это осмысленная логика или тривиальщина; какой сценарий его
   исполнит.
4. **Оцени существующие тесты заодно** (см. «Ключевой принцип»): нет ли
   фиктивных ассертов, тавтологий, тестов, которые физически не могут упасть,
   over-mock (тест проверяет мок, а не код). Помечай слабые тесты — их часто
   надо укрепить прежде, чем добавлять новые.
5. **Спроектируй недостающие тесты по техникам** (см. чек-лист ниже) — от
   классов эквивалентности и границ до негативных/исключительных путей.
   Расставь по приоритету риска.
6. **Напиши тесты** в конвенции проекта (та же тестовая директория, стиль
   фикстур/моков, нейминг). Мокай только внешние границы (сеть, БД, время,
   ФС), а не саму проверяемую логику.
7. **ЗАПУСТИ тесты** — они должны проходить. Затем **перемерь покрытие** и
   покажи прирост (было X% строк / Y% веток → стало). Приложи вывод раннера.
8. **Зафиксируй остаточные gaps** — что осталось непокрытым сознательно
   (тривиальщина, недостижимый код, требует интеграционного окружения) и
   почему.

## ТЕХНИКИ ПРОЕКТИРОВАНИЯ ТЕСТОВ (применяй релевантные периметру)

1. **Классы эквивалентности (equivalence partitioning)**
   - Раздели входное пространство на классы, дающие одинаковое поведение. На
     каждый валидный класс — минимум один позитивный кейс, на каждый
     невалидный — свой негативный. Не 20 кейсов из одного класса, а по одному
     на класс.
2. **Граничные значения (boundary value analysis)**
   - На каждую границу — по кейсу с обеих сторон: `min-1, min, max, max+1`.
     Классика багов: `>` vs `>=`, off-by-one, пустая коллекция vs один
     элемент, 0 и отрицательные, переполнение, максимальная длина строки.
3. **Таблицы решений (decision tables)**
   - Для функции с несколькими булевыми условиями — покрой значимые
     комбинации условий, а не только «все true / все false». Особенно ветку,
     где условия конфликтуют.
4. **State transition**
   - Для сущностей с состояниями (заказ: new→paid→shipped) — проверь валидные
     переходы И запрещённые (нельзя из shipped в new). Идемпотентность
     повторного перехода.
5. **Негативные и исключительные пути** — обычно самые непокрытые:
   - Невалидный ввод, null/None/undefined, пустые строки/коллекции, неверный
     тип, отсутствующий ключ.
   - Ошибки зависимостей: таймаут сети, отказ БД, исключение внешнего
     сервиса — проверь, что код обрабатывает их так, как заявлено (ретрай,
     деградация, проброс, конкретное сообщение), а не «падает молча».
   - Проверяй тип исключения И сообщение/код, а не только сам факт бросания.
6. **Mocking внешних зависимостей**
   - Изолируй unit от сети/БД/времени/ФС/ГСЧ. Мокай на границе (клиент, репо,
     `now()`), а не внутреннюю логику. Проверяй и результат, и факт вызова
     зависимости с правильными аргументами (где это часть контракта).
7. **Параметризация**
   - Родственные кейсы (много входов → правило) объединяй в
     параметризованный тест (`@pytest.mark.parametrize`, `test.each`,
     table-driven в Go), а не копипасть тело.
8. **Property-based (если применимо и есть инструмент)**
   - Для чистых функций с инвариантами (сериализация/десериализация,
     сортировка, парсинг) рассмотри Hypothesis/fast-check — ловит границы,
     которые не придумаешь руками.

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

- **Ветвь есть, но обе стороны не покрыты одинаково**: `if x:` покрыт только
  по true — ветка false невидима в line coverage, ловится только branch
  coverage.
- **Обработчики исключений** (`except`/`catch`/`rescue`) — почти всегда
  непокрыты, потому что «сложно вызвать ошибку». Именно там живут баги.
- **Ранние `return`/`guard`-условия** — валидация на входе функции, ранние
  выходы; тесты обычно бьют только в «счастливый путь» мимо них.
- **Дефолтные значения аргументов и `else`-ветки** switch/match без явного
  default.
- **Пустая коллекция vs один элемент vs много** — три разных класса, а тест
  часто один (на «много»).
- **Асинхронный код**: непокрытые ветки внутри `await`/промисов, отмена,
  таймаут, одновременные вызовы.
- **Конкурентность/идемпотентность**: повторный вызов, дубликат сообщения из
  очереди, retry — приводит ли к двойному эффекту.
- **Границы времени/таймзон/DST**, если код работает с датами (замокай время,
  не полагайся на реальное `now()`).
- **Числовая точность**: деньги во float, округление, деление на ноль.
- **Мультитенантность/права** (если применимо): фильтрация по
  tenant_id/владельцу — есть ли негативный тест «чужой объект недоступен».
- **Тесты, которые не падают при поломке** — уже существующие «зелёные»
  тесты, дающие ложное чувство покрытия; проверь их мутационным мышлением.
- **Код, покрытый только косвенно** через другой тест: строка исполняется, но
  её конкретное поведение никто не ассертит.

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

- Базовое и итоговое покрытие по периметру измерены инструментом и приложены
  (строки И ветви), прирост показан числами.
- Приоритетные (по риску) непокрытые участки закрыты содержательными
  тестами; оставленные gaps перечислены с обоснованием.
- Все новые тесты запущены и проходят (вывод раннера приложен).
- Ни один новый тест не является assertion-free/тавтологией; каждый упадёт
  при реальной поломке проверяемой логики (проверено мутационным мышлением
  или прогоном mutation testing, если он есть).
- Новые тесты соответствуют конвенции проекта (директория, стиль, нейминг),
  не сломали существующий набор, детерминированы (не flaky).

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

1. **Итог одной фразой**: покрытие по периметру поднято с X%/Y веток до
   X'%/Y' веток; добавлено N тестов на приоритетную логику; остаточные gaps —
   в основном тривиальны/требуют интеграционного окружения.
2. **SCOPE** — что именно покрывалось (файлы/модули) и что осталось за
   периметром и почему.
3. **Тест-стек и инструмент покрытия** — что определено в репозитории и какой
   командой мерилось.
4. **Карта пробелов по риску** — таблица: file:line непокрытого участка →
   класс риска (высокий/средний/низкий) → какой сценарий не проверялся.
   Сначала высокий риск.
5. **Что добавлено** — список написанных тестов (файл, что проверяет, какой
   пробел закрывает), с указанием применённой техники.
6. **Замечания к существующим тестам** — найденные фиктивные/слабые тесты
   (file:line) и что с ними сделано/рекомендуется.
7. **Прирост покрытия** — числа «до/после» (строки и ветви) + приложенный
   вывод раннера.
8. **Остаточные gaps** — что осознанно НЕ покрыто и почему.
9. **Что не удалось проверить** — ограничения (нет интеграционного окружения,
   недоступна внешняя система, mutation testing не настроен и т.п.).

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

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

Это авторский скилл: код тестов пиши так, чтобы он проходил, был
детерминированным и поддерживаемым, проверял поведение (а не подгонял
процент), и следовал конвенциям проекта. Реальные баги в продуктовом коде,
которые вскроются при написании тестов, не «замазывай» тестом под текущее
(неверное) поведение — вынеси их отдельным пунктом в отчёт.

