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
доступ заборонено, токен відсутній, заборонено дивитися secrets, токени і права job

Тут корисно памʼятати, що класифікація потрібна не заради красивої таблиці, а заради мінімального наступного кроку. Помилилися з типом — лагодите не те. Lockfile mismatch інколи «виправляють» правкою application-коду — ефект, якби ви лагодили чайник перестановкою стільців на кухні.

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

4. Просіть розбір, а не виправлення

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

Шаблон запиту:

Проаналізуйте цей лог збою CI.
Поверніть:
1. тип падіння,
2. ймовірну першопричину,
3. докази з файлів/команд,
4. мінімальний план фіксу,
5. що повторно запускати після фіксу.
Не редагуйте код поки що.

Цей останній рядок — Не редагуйте код поки що — особливо важливий: він пригальмовує типовий цикл поспішних патчів, коли 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
Опитування
Non-interactive mode і CI, рівень 21, лекція 4
Недоступний
Non-interactive mode і CI
Non-interactive Claude, GitHub Actions, Docker і розбір CI failures
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ