Автоматизація інтеграційних тестів у CI/CD: Docker і паралелізм

Прогін інтеграційних тестів розтягується на години й гальмує кожен реліз. Ми автоматизуємо інтеграційні тести в CI/CD під ключ: налаштовуємо паралельний запуск, ізоляцію середовищ і відтворювані тестові контури. Час прогону скорочується в рази, а релізи виходять передбачувано й без ручних перевірок.
Часті запитання

Чому інтеграційні тести CI/CD гальмують викатку

Конвеєр зупиняється не на збірці образу, а на етапі, де тести починають ходити в реальні залежності: базу, чергу повідомлень, зовнішній сервіс.

Якщо прогін послідовний, кожен сценарій спочатку піднімає свій стенд, чекає на його готовність і лише потім робить перший запит — на десятках сценаріїв це години очікування, які оплачує кожна викатка.

Окрема біда — коли середовище одне на всіх. Тести пишуть у спільні таблиці, бачать дані сусіднього сценарію і падають з причин, не пов'язаних зі змінами в коді.

Відтворити таке падіння локально майже неможливо: на машині розробника інший порядок виконання й інший набір даних, тому розбір розтягується на години, а не на хвилини.

Далі вмикається головний руйнівник — хибні падіння. Пара нестабільних сценаріїв фарбує збірку в червоний, її перезапускають вручну, і незабаром результат перестають читати: зміни зливають поверх збірки, що впала.

Так час збірки й нестабільний конвеєр перетворюються на один і той самий ризик — в одному випуску накопичується дедалі більше правок, і шукати причину збою в бойовому середовищі доводиться по всьому накопиченому обсязі.

Розбираємо, як це закривається: ізоляцією середовищ на рівні Docker, паралельним прогоном незалежних наборів і відсіканням нестабільних сценаріїв ще до того, як вони потраплять в обов'язковий набір.

Паралельний запуск та ізоляція середовищ тестів

Незалежні потоки замість одного довгого прогону

Набір тестів ділиться на групи, які не ділять між собою ні дані, ні зовнішні ресурси, і групи йдуть паралельно в окремих контейнерах. Кожен потік піднімає своє середовище з того самого образу, тому результат не залежить від того, що встиг зробити сусід по прогону.

Тривалість визначається найдовшою групою, а не сумою всіх: за рівномірного поділу прискорення близьке до кількості потоків.

Ізоляція тестових даних

Кожен потік працює у власній схемі бази або в окремому екземплярі, піднятому на час прогону, а не в спільній тестовій базі. Дані створюються скриптом підготовки до старту й видаляються після, тому сценарій оновлення замовлення не побачить запис, створений паралельним тестом.

Ізоляція тестових даних прибирає цілий клас плаваючих падінь, які відтворювалися лише за певного порядку виконання.

Стабільність прогону та чесні повтори

Повтор запускається лише для операцій, де допустима мережева нестабільність, а не для тесту загалом. Звіт позначає падіння як інфраструктурне або логічне, тому червона збірка знову означає дефект, а не шум. Частка хибних падінь падає в рази, і команда перестає ігнорувати повідомлення про збірку.

Швидке відновлення збірки

Час відновлення збірки скорочується завдяки тому, що перезапускається лише група, яка впала, на прогрітому кеші залежностей, а не весь набір загалом.

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

Паралельний запуск тестів та ізоляція середовищ разом дають передбачувану тривалість: розкид між прогонами вимірюється десятками секунд, а не залежить від того, які сценарії збіглися в одному запуску.

Варіанти організації тестового середовища

Вибір середовища визначає дві речі: скільки тестів можна гнати одночасно і наскільки стабільний результат. Інтеграційні тести залежать від бази, черг і зовнішніх сервісів — на спільному стенді прогони конкурують за дані й падають з причин, не пов'язаних з кодом.

Головний вододіл — середовище живе постійно чи піднімається на час прогону. Перше стартує миттєво й годиться для ручного приймання, друге дає ізоляцію та чесний паралелізм. Нижче — варіанти, між якими реально обирають.

Варіант Коли підходить Обмеження підходу
Постійний стенд Рідкісні прогони, приймання, інтеграції із зовнішніми системами, закритими для тестового середовища Конфігурація дрейфує, стан накопичується, тести заважають одне одному, паралелізм майже неможливий
Контейнери для тестів на кожен прогін (ефемерне середовище) Регулярні прогони на кожну зміну, багато гілок, потрібен паралельний запуск Час на підйом та ініціалізацію, потрібні готові образи, міграції та фікстури, обов'язкове очищення ресурсів
Гібрид: свої сервіси в контейнерах, зовнішні — через тестові двійники Частина залежностей недоступна з тестового контуру або нестабільна Двійник розходиться з реальною поведінкою, контракти потрібно оновлювати разом із зовнішньою системою
Локальний запуск у розробника Налагодження конкретного сценарію до надсилання змін Не відтворює середовище збірки, розходження версій залежностей, ручні кроки не автоматизуються

На практиці найчастіше беруть ефемерне середовище для основного набору та постійний стенд для сценаріїв, які не можна ізолювати.

Порядок впровадження: від аудиту до передачі

Маршрут побудований так, щоб кожен крок давав перевірюваний результат і не зупиняв поточний випуск. Спершу фіксуємо, як конвеєр працює зараз, — лише потім змінюємо порядок прогону.

  1. Аудит конвеєра збірки. Розбираємо, де йде час: підготовка середовища, дубльовані кроки, тести, що падають не через код. На виході — карта вузьких місць з оцінкою виграшу за кожним пунктом.
  2. Схема тестового середовища. Визначаємо склад сервісів на один прогін, спосіб ізоляції баз і черг, порядок наповнення тестовими даними. Схема узгоджується до робіт: вона задає, що взагалі можна розпаралелити.
  3. Налаштування контейнерів. Збираємо образи із закешованими залежностями, замість фіксованих пауз ставимо перевірки готовності сервісів. Підготовка середовища перестає бути найдовшою стадією.
  4. Розпаралелювання. Ділимо набір на незалежні групи, розводимо їх по окремих копіях середовища, прибираємо спільні змінювані ресурси. Час прогону скорочується приблизно пропорційно кількості груп.
  5. Перевірка стабільності. Ганяємо набір серіями, позначаємо нестабільні тести й лікуємо причини: гонки, спільні дані, залежність від порядку запуску. Карантин оформлюємо задачею на виправлення, а не виключенням із набору.
  6. Передача в експлуатацію. Віддаємо конфігурації, схему середовища, регламент і розбір типових збоїв, проводимо розбір з командою. Далі правки вносяться за документованим порядком, а не з пам'яті.

Конфігурації, схеми та регламенти на виході

Передаємо не «конвеєр, який працює в нас», а комплект, який команда замовника веде сама. Нижче — що фізично залишається в репозиторії та у вікі після здачі: конфігурації, схеми, політики й документи, за якими новий інженер піднімає стенд і розбирає падіння без листування з нами.

  • Файли конфігурації збірки — опис етапів, кешування шарів образів, матриця запуску за гілками: тести ганяються на кожній зміні, а не за розкладом.
  • Схема середовища — які сервіси піднімаються для інтеграційного прогону, їхні зв'язки, порядок ініціалізації та точки очікування готовності; схема збігається з реальним стендом.
  • Правила доступу до секретів — де зберігаються облікові дані, що видно з pull request з форків, як секрет підставляється лише в захищені гілки й не потрапляє в логи.
  • Політика паралелізму — як дробляться набори тестів, скільки копій середовища піднімається одночасно і за якою ознакою тести визнаються незалежними.
  • Документація з тестів — що покриває кожен набір, які дані для нього потрібні, як відтворити конкретний сценарій локально однією командою.
  • Регламент розбору падінь — хто дивиться прогін, що впав, за який час, як відрізняти нестабільний тест від реальної регресії та коли тест знімається з прогону.
  • Шаблони звітів і оповіщень — куди йде результат, які зрізи бачить черговий, де історія прогонів за останні тижні.
  • Порядок змін — як правки в конфігурацію тестів проходять рев'ю та перевіряються на самій інфраструктурі, а не після викладення.

Комплект розрахований на передачу: команда замовника змінює набори й середовища сама, а ми підключаємося лише там, де потрібне доопрацювання самої інфраструктури.

Кейс: як час прогону скоротився втричі

Проєкт — сервіс із десятком пов'язаних компонентів: програмний інтерфейс, черга, дві бази та зовнішній платіжний контур. Інтеграційні тести ганялися на спільному стенді, який жив своїм життям: дані від минулих прогонів, чужі гілки, ручні правки.

Прогін займав понад сорок хвилин, а приблизно кожне п'яте падіння було хибним — тест падав не через код, а через середовище.

Команда перестала довіряти сигналу: червону збірку перевіряли руками, перезапускали «про всяк випадок», реліз виходив раз на два тижні. Нижче — що саме змінили і чим це вимірювалося.

Що зробили

Кожен прогін отримав ізольований набір контейнерів, який піднімається на час тесту й прибирається після. Стан баз більше не успадковується: схема накочується з нуля, початкові дані задаються фікстурами.

Конвеєр на GitLab CI замість одного довгого етапу запускає паралельні групи за доменами — замовлення, платежі, повідомлення, — кожна у своєму середовищі.

Зовнішні залежності замінили заглушками з фіксованими сценаріями, включно з таймаутами та частковими відмовами. Замість фіксованої паузи перед тестами з'явилося очікування готовності сервісів — це прибрало клас падінь «не встигло піднятися».

Що стало

Показник До Після
Тривалість прогону 40+ хвилин 12 хвилин
Частка хибних падінь ~20% 2%
Ручні перезапуски за тиждень десятки одиниці
Частота випусків раз на два тижні 2–3 рази на тиждень

Сигнал знову означає те, що має: червоний прогін — це реальна поломка в коді, а не збій стенда. Час, що звільнився, пішов не в звітність, а в скорочення інтервалу між правкою та її перевіркою на живому середовищі.

Чим інтеграційні тести CI/CD відрізняються від модульних

Межа між рівнями проходить по процесу й мережі. Модульний тест живе всередині однієї одиниці коду: залежності підміняються заглушками, перевіряються розгалуження, перетворення даних і обробка помилок.

Він не відповідає на питання, чи правильно зібраний запит до бази, чи дійшло повідомлення до черги і чи збіглася схема відповіді зовнішнього сервісу.

Інтеграційний тест перевіряє саме це — стики сервісів. База піднімається в контейнері, брокер і зовнішні API замінюються передбачуваними заглушками, і набір фіксує контракти, міграції, відкат транзакцій, повтори за мережевих збоїв і серіалізацію. Такий прогін ловить клас дефектів, недосяжний на рівні модулів.

Змішувати обидва рівні в одному прогоні дорого з двох причин. Інтеграційний набір на порядок повільніший: підйом середовища й очікування готовності сервісів з'їдають основну частину часу.

І діагностика гірша — падіння не каже, де дефект: у логіці, у схемі даних чи в неготовому середовищі.

Звідси піраміда тестів: багато швидких модульних, менше інтеграційних, одиниці наскрізних сценаріїв. Частки підбираються так, щоб основну масу дефектів знімали дешеві рівні, а дорогі перевіряли те, чого вони фізично не бачать.

Розділення наборів закріплюється технічно: окремі команди запуску, маркування, різні стадії конвеєра. Модульні йдуть на кожну зміну в гілці й дають зворотний зв'язок за хвилини, інтеграційні — на злиття в основну гілку й перед випуском, з паралелізмом за контейнерами. Так повільний рівень не гальмує розробку, а швидкий не вдає, що перевірив стики.

Технічні сумніви та обмеження підходу

Сумніви тут доречні: інтеграційні тести залежать від середовища, і наївне перенесення їх у конвеєр дає нестабільний результат замість сигналу. На практиці більшість обмежень закривається налаштуванням запуску, а не переписуванням самих перевірок.

Тести ходять у зовнішні сервіси — що з ними робити?

Зовнішні залежності замінюємо контейнерами-емуляторами, які піднімаються поряд із тестом у тій самій мережі: брокер, сховище, сторонній HTTP-інтерфейс. Контракт фіксуємо окремою перевіркою схеми відповіді, щоб заглушка не розійшлася з реальним сервісом.

Для систем, недоступних ззовні, записуємо реальні відповіді й відтворюємо їх, але раз на добу проганяємо набір проти справжнього контуру.

Чи потрібен постійний стенд?

Ні. Середовище описується декларативно й піднімається на час прогону, тому тест стартує на чистій базі й не успадковує дані минулих запусків. Постійний контур тримаємо лише для приймальних перевірок і демонстрацій.

Де межа паралелізму?

Обмеження одне — спільний стан. Тести, що пишуть в одну схему, починають заважати одне одному: блокування, гонки, хибні падіння. Вирішується ізоляцією — своя схема або префікс на потік, обгортка в транзакцію з відкатом, окремий екземпляр бази для найважчих наборів. Після цього кількість паралельних потоків упирається вже в ресурси вузла, а не в конфлікти.

Чи можна прискорити прогін без переписування тестів?

Так, і насамперед за рахунок підготовки середовища: кеш шарів образу, зібраний образ застосунку замість збірки на кожному запуску, попереднє завантаження залежностей.

Далі — розділення набору на швидкі та повільні групи, повтор лише перевірок, що впали, прогрів з'єднань перед першим запитом. Тестові дані готуємо міграціями й сідами зі снапшота, а не накопиченням вручну.

Обговоримо задачу й повернемося з оцінкою та планом

Щоб повернутися з планом, а не із загальними словами, потрібен контекст вашої збірки. Опишіть задачу: які сценарії мають проходити на кожен коміт, скільки зараз іде прогін і які тести падають найчастіше. Цього достатньо, щоб розмова була предметною.

На созвоні з інженером розберемо, де вузьке місце — запуск середовищ у Docker, послідовний прогін чи нестабільні тести, — і що реально прискорити паралелізмом. Що надіслати заздалегідь і що уточнимо:

  • поточний файл конфігурації збірки та список сервісів, які піднімаються для тестів;
  • де виконується збірка: свої сервери чи зовнішній майданчик, чи є обмеження щодо доступів;
  • перелік сценаріїв, які потрібно покрити в першу чергу, і тих, що можна відкласти;
  • як зараз зберігаються тестові дані та чи є середовище, близьке до робочого;
  • вимоги до відкату: що має відбуватися, якщо прогін упав на етапі викладення;
  • хто з боку вашої команди прийматиме роботу і за якими ознаками вважатиме її готовою.

У відповідь надсилаємо план робіт з автоматизації: етапи, залежності між ними та оцінку термінів за кожним. Окремим пунктом — підготовка до старту: що налаштуємо на вашому боці до першого прогону і що знадобиться від ваших інженерів. Узгодимо план — починаємо.

Наші компетенції:

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1394
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1369
  • Розробка логотипу компанії B2B Advance
    Розробка логотипу компанії B2B Advance
    816
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1141