JavaRush /Курсы /Claude code /Тесты в CI и разбор падений

Тесты в CI и разбор падений

Claude code
21 уровень , 4 лекция
Открыта

1. «У меня работает» — это ещё не доказательство

До этого момента вы в основном запускали проверки рядом с собой: терминал, IDE, иногда контейнер — быстро и с отличной обратной связью. Но для команды этого мало: локальный успех и воспроизводимое доказательство для всех — не одно и то же, даже если очень хочется в это верить.

В packaging-примере я сузил фокус до одного сервиса, чтобы собрать образ и пройти smoke-check. Теперь вернёмся к обычному PR-уровню Commerce OS: те же build и smoke-проверки становятся частью общей backend-проверки и разбора красных job.

Когда вы на своей машине запускаете ./gradlew test, вы получаете локальный verification harness: поймать ошибку до коммита. Но в pull request важно уже не то, что видели вы, а то, что увидит любой в стандартной среде. Так появляется CI как remote verification boundary.

Чтобы не путаться в терминах, удобно держать перед глазами маленькую таблицу:

Понятие На какой вопрос отвечает Где живёт
Локальные проверки Что я проверяю локально до push? ваш ноутбук, локальный контейнер, IDE
CI verification Как те же проверки автоматически доказываются в общей среде? CI runner
Human review / merge decision Что команда делает с этим доказательством? review и правила merge

Сегодня нас интересует именно CI verification: не «кто разрешил merge», а «какие проверки автоматически выполнились и что они доказали».

На практике это спасает от древнего заклинания works on my machine. Заклинание популярное, но карьеру в инфраструктурной части разработки на нём не построишь. У вас локально прогрет кэш, выставлены переменные окружения, база поднята со вчерашнего вечера. А CI получает пустую чистую среду и честно отвечает: «Ваш проект без ручной магии не поднимается». Он не вредничает. Он впервые говорит вам правду.

Именно поэтому автоматизация тестов в CI — не дубль локальной работы, а её взрослая версия: те же проверки в стандартной среде, где команда доверяет результату, а не вашему настроению.

2. Job начинается с вопроса «какие доказательства нам нужны»

Когда человек слышит фразу «автоматизация тестов в CI», в голове часто рождается страшный YAML на пол-экрана. Начинайте не с синтаксиса, а с вопроса: какие доказательства нужны после каждого PR в Commerce OS. Ответите — и job перестаёт быть древним манускриптом.

Для нашего Commerce OS после обычного backend-изменения минимальный набор доказательств обычно такой: модульные тесты проходят, интеграционные проходят, приложение собирается в артефакт, smoke check показывает, что сервис хотя бы стартует. Не потому что мы любим запускать побольше команд, а потому что каждая отвечает за свой слой доверия.

Вот сердцевина test job без служебных деталей платформы:

jobs:
  verify-backend:
    runs-on: ubuntu-latest
    steps:
      - run: ./gradlew test
      - run: ./gradlew integrationTest
      - run: ./gradlew bootJar

Это намеренно короткий фрагмент. Рядом в реальном workflow будут checkout, настройка Java, кеширование и, возможно, тестовая база. Но если показать всё сразу, начинающие часто перестают видеть главное. А главное здесь простое: job превращает договорённости о проверках в повторяемые команды.

Те же проверки в локальном виде читаются вообще почти по-человечески:

./gradlew test            # unit и сервисные тесты
./gradlew integrationTest # HTTP, база и конфигурация
./gradlew bootJar         # приложение собирается в jar

После сборки полезно добавить совсем маленький smoke check. Не полноценный end-to-end сценарий, а именно быстрый ответ на вопрос «оно вообще запускается?».

java -jar build/libs/commerce-os.jar &                  # сервис стартует в фоне
curl -s http://localhost:8080/actuator/health          # {"status":"UP"}

У этого набора проверок есть вполне земная логика:

Проверка Что она доказывает Что обычно означает её падение
test
отдельные классы и сервисы ведут себя ожидаемо ошибка в коде или в ожиданиях теста
integrationTest
слои приложения правильно работают вместе проблема в API, базе, конфигурации, сценарии
bootJar
проект собирается в артефакт ошибка в зависимостях, сборке или конфигурации
smoke check приложение стартует и отвечает на базовый запрос проблема в запуске, портах, env vars, health endpoint

Если ваш PR затрагивает ещё и фронтенд Next.js, рядом обычно живёт похожий frontend job с npm ci, тестами и своим smoke-сценарием. Но логика не меняется: каждая команда — не просто действие, а маленькое доказательство.

И ещё один спокойный, но важный момент. Точные имена steps, версии action и синтаксис провайдера CI меняются. Навык — понимать структуру job, а детали сверять в документации вашей CI-платформы. Учите идею, а не влюбляйтесь в случайную строку YAML.

3. Красный крестик — не приговор, а первый сигнал

Самый бесполезный способ смотреть на CI — увидеть красный крестик и подумать: «Ну всё, пайплайн меня ненавидит». Логи почти всегда честны: редко изящны, иногда шумны, но говорят, что именно пошло не так. Ищите не весь красный, а первый осмысленный сигнал.

Хорошее правило для начала: не читайте лог сверху вниз как роман. Сначала найдите упавший step, потом первую содержательную ошибку внутри него, а уже после — контекст. CI тянет длинный шлейф вторичных ошибок, но корень сидит в первых строках.

Например, вот такой фрагмент довольно прозрачен:

> Task :integrationTest FAILED
RefundInboxApiTest > shouldReturnNewestRefundFirst() FAILED
org.opentest4j.AssertionFailedError:
expected: <REF-103> but was: <REF-101>

А это уже другая история:

npm ERR! code EUSAGE
npm ERR! `npm ci` can only install packages when package-lock.json is in sync

Оба лога красные. Первый — про поведение приложения или теста, второй — про несинхронизированный lockfile. Одинаковый цвет, совершенно разные проблемы.

Чтобы быстрее ориентироваться, удобно использовать классификацию падений:

Тип падения Как обычно выглядит С чего начинать
code
assertion, exception, неверный ответ сервиса смотреть изменённый код и affected files
test
неверный тестовый сценарий, устаревшее ожидание проверить, тест правда выражает нужное поведение
environment
не поднялся сервис, неверный порт, missing env сверить конфигурацию job и runtime-переменные
dependency
npm ci, Gradle, lockfile, несовместимые версии проверить lockfile и зависимости в PR
flaky
то падает, то проходит без изменений подтвердить нестабильность и зафиксировать evidence
infrastructure
runner умер, сеть отвалилась, timeout платформы проверить статус платформы и повторить после восстановления
permissions
access denied, token missing, forbidden смотреть secrets, токены и права job

Здесь полезно помнить, что классификация нужна не ради красивой таблички, а ради минимального следующего шага. Ошиблись с типом — чините не то. Lockfile mismatch иногда «исправляют» правкой application-кода — эффект, как если бы вы чинили чайник перестановкой стульев на кухне.

Отдельная хитрость связана с разницей между code и test. Продукт осознанно поменялся, а тест ждёт старое поведение — это test failure. Тест выражает правильное бизнес-правило, а код его нарушил — это code failure, даже если упал тест. Спрашивайте не «что упало последним», а «где источник ошибки ближе всего к исправлению».

4. Просите разбор, а не починку

Когда CI падает, руки очень чешутся написать Claude что-нибудь в духе: «Почини pipeline». Желание понятное, но результат обычно печальный. Агент азартно трогает код, конфиги, тесты, иногда полпроекта сразу — и потом в diff уже не понять, где закончился диагноз и начался творческий порыв. Сначала просите разбор, а не починку.

Шаблон запроса:

Analyze this CI failure log.
Вернуть:
1. failure type,
2. likely root cause,
3. file/command evidence,
4. minimal fix plan,
5. what to rerun after fix.
Do not edit code yet.

Эта последняя строка — Do not edit code yet — особенно важна: она тормозит типичный цикл поспешных патчей, когда Claude видит красный лог и решает немедленно спасать мир. Нам пока не нужен герой. Нам нужен внимательный аналитик.

Если вы ведёте работу аккуратно, результат такого анализа лучше не держать только в голове или в чате. Его стоит сразу зафиксировать в EVIDENCE_LOG.md, чтобы у PR была понятная история. Например:

## Сбой CI
- Тип: dependency
- Шаг: `npm ci`
- Доказательство: `package-lock.json` не синхронизирован с `package.json`
- Минимальный фикс: пересобрать lockfile и закоммитить его
- Повторный запуск: только `verify-frontend`

Это может показаться формальностью, но на практике такая запись очень помогает. Через час вы уже не помните, почему rerun делали только для одного job; через неделю весь эпизод — туман. EVIDENCE_LOG.md нужен, чтобы из тумана оставался инженерный след.

В командах, где есть Workflow Kit, такой шаблон часто превращают в маленький reusable skill. Но и без него сам контракт анализа делает работу спокойнее: Claude перестаёт быть «автоматическим ремонтником» и становится аккуратным помощником по первичному разбору.

5. Один зелёный rerun ничего не «починил»

Отдельная ловушка CI — лечить симптомы. Упал тест? Давайте временно закомментируем. Со второго раза прошло? Наверное, всё нормально. Так заводятся самые неприятные призраки: проблемы вроде исчезли, но возвращаются в неудобный момент. Обычно ночью. Обычно перед релизом. У них хороший вкус.

Поэтому после классификации нужен спокойный алгоритм действий:

flowchart TD
A[CI упал] --> B[Классифицируем тип падения]
B --> C[Фиксируем evidence]
C --> D[Делаем минимальный фикс]
D --> E[Повторяем только нужный job]
E --> F[Обновляем EVIDENCE_LOG.md]

Ключевое слово здесь — только нужный. Поменяли backend-сортировку и упал verify-backend — нет смысла гонять весь pipeline с unrelated jobs. Targeted rerun экономит время и делает диагностику честнее: проверяется ровно та гипотеза, которую только что исправили.

Удобно держать такое соответствие:

Тип падения Что обычно исправляем Что обычно повторяем
code / test
код или сам тест конкретный job, где упал тест
dependency
lockfile, версия зависимости, конфиг сборки связанный backend или frontend job
environment
env vars, сервисы, порты, запуск job с этой средой
permissions
token, secrets, права job job после обновления прав
flaky
сначала подтверждаем нестабильность один адресный rerun проблемного job
infrastructure
обычно ничего в коде после восстановления иногда повторяют весь workflow

Про flaky-тесты важно понимать одну неприятную, но полезную вещь: единичный успешный rerun не «исправил» проблему, он лишь показал, что тест бывает нестабильным. Тест без изменения кода то падает, то проходит — это технический долг, его надо явно зафиксировать. CI как раз и существует, чтобы отделять удачу от инженерии.

Именно поэтому «временно выключить тест» почти всегда худшее решение. Оно очень сладкое, потому что даёт мгновенный зелёный pipeline. Но взамен вы лишаетесь датчика, который пытался сказать правду. Сломанный как инструмент тест чините или изолируйте осознанно и заметно, а не убирайте тихо с дороги.

6. Сценарий: от красного job к фиксу

Давайте приземлим всё на знакомый проект, чтобы CI перестал быть абстракцией. Возьмём Commerce OS и баг, который уже встречался нам раньше: в inbox возвратов должны идти самые свежие refund-заявки первыми. Разработчик исправил сортировку, локально глянул данные — вроде красиво. PR ушёл, CI вернул красный verify-backend.

Лог в GitHub Actions показал вот это:

> Task :integrationTest FAILED
RefundInboxApiTest > shouldReturnNewestRefundFirst() FAILED
expected: <REF-103> but was: <REF-101>

Первый здравый шаг — не открывать пять файлов и не просить Claude «сделай как надо». Классифицируем падение как code: тест проверяет порядок элементов, а сервис сортирует неправильно.

Дальше мы даём Claude узкий запрос на анализ, а не на массовый ремонт. Claude возвращает: падение в порядке сортировки в RefundInboxService, тест выражает корректное бизнес-ожидание, минимальный фикс — обратная сортировка по createdAt, а после повторить только verify-backend.

Тест, который нас защищает, может выглядеть так:

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

@Test
void shouldReturnNewestRefundFirst() {
    var ids = service.findRefundIdsForInbox();
    assertEquals("REF-103", ids.get(0));
}

А минимальный фикс в сервисе — вот так:

import static java.util.Comparator.comparing;

return refunds.stream()
        .sorted(comparing(RefundRequest::getCreatedAt).reversed())
        .map(RefundRequest::getId)
        .toList();

Обратите внимание, чего здесь нет. Мы не трогали контроллер, не переписывали DTO, не меняли базу, не добавляли framework, не устраивали «небольшой рефакторинг заодно». Это и есть controlled fix: один симптом, одна проверяемая причина, один понятный diff.

После фикса запись в EVIDENCE_LOG.md:

## Сбой CI
- Тип: code
- Шаг: `integrationTest`
- Доказательство: `RefundInboxApiTest` ожидал новый порядок, сервис сортировал по возрастанию
- Минимальный фикс: обратная сортировка по `createdAt` в `RefundInboxService`
- Повторный запуск: только `verify-backend`

Теперь мы делаем адресный rerun именно backend job. Он проходит, а вместе с ним unit, integration и smoke — и вместо локального «вроде починил» у вас удалённо воспроизводимое доказательство. CI перестаёт быть страшным красным крестиком и становится тем, чем должен быть: спокойным, строгим и довольно полезным проверяющим, который одинаково видит ваш код сегодня, завтра и у любого другого в команде.

Когда тесты, build и разбор падений уже дают такое воспроизводимое доказательство, дальше естественно возникает вопрос о границах принятия решений: какие проверки становятся обязательными quality gates и какие артефакты должны переживать сам job.

1
Задача
Claude code, 21 уровень, 4 лекция
Недоступна
Извлечение ключевых строк из CI failure log через терминал
Извлечение ключевых строк из CI failure log через терминал
1
Задача
Claude code, 21 уровень, 4 лекция
Недоступна
Классификация падения CI внутри Claude CLI
Классификация падения CI внутри Claude CLI
1
Опрос
Non-interactive mode и CI, 21 уровень, 4 лекция
Недоступен
Non-interactive mode и CI
Non-interactive Claude, GitHub Actions, Docker и разбор CI failures
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ