vyvchy
    Теми розділу

    06 · Автоматизація: стратегія

    Тестування мікросервісів: стратегія

    Зміст

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

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

    Моноліт vs мікросервіси очима QA

    Моноліт (monolith) — це один застосунок, який деплоять цілком. Виклики між його частинами відбуваються всередині одного процесу — це звичайні виклики функцій: миттєві, надійні, або впали разом з усім процесом. Дані зазвичай лежать в одній базі, тож цілісність гарантує . Для QA це зручно: є одне середовище, яке цілком реальне, один деплой, одна версія. Наскрізний тест через UI чи API перевіряє систему такою, якою вона поїде в прод.

    Мікросервіси (microservices) розбивають цю монолітну брилу на багато незалежних сервісів. Кожен має власну кодову базу, власну базу даних і власний цикл релізу. Спілкуються вони вже через мережу — синхронно (HTTP/gRPC) або асинхронно (черги повідомлень, події). Головна зміна для тестувальника: поведінка системи більше не «живе» в одному місці — вона виникає з взаємодій. Замовлення проходить через сервіс кошика, сервіс оплати, сервіс складу, сервіс нотифікацій, і кожен стик між ними — це мережа, з усіма її радощами: , , часткові , .

    АспектМонолітМікросервіси
    Виклик між компонентамиВиклик функції в процесіМережевий запит
    ДаніОдна база, транзакціїБаза на сервіс, eventual consistency
    ДеплойОдин, цілкомНезалежний на кожен сервіс
    «Реальне» середовищеОдне, повнеНемає одного повного й стабільного
    Де живе баг інтеграціїВсередині кодуНа стиках, у мережі
    Локалізація падінняОдин стек-трейсТреба знайти винний сервіс

    Ключовий висновок: у моноліті найдорожчий — логіка всередині; у мікросервісах він зміщується на межі між сервісами. Саме туди має дивитися стратегія тестування.

    Чому e2e в мікросервісній архітектурі болить

    Наскрізний (end-to-end, e2e) тест проганяє реальний користувацький сценарій через усі сервіси одразу. У моноліті це нормальний основний інструмент. У мікросервісах — джерело постійного болю, і ось чому.

    Крихкість множиться. Якщо сценарій зачіпає вісім сервісів і кожен окремо доступний у 99% часу, спільна доступність усього ланцюга — це вже приблизно 0.99^8, тобто близько 92%. Приблизно кожен тринадцятий прогін червоний не через баг, а тому що якийсь один сервіс саме перевантажений, редеплоїться чи моргнув мережею. Тест перетворюється на генератор шуму.

    накопичується. Кожен мережевий стик додає асинхронність, таймаути й eventual consistency (). Це не абстракція з підручника, а режим за замовчуванням у реальних сховищах: дока DynamoDB описує його так — «the responses may not reflect the results of a recently completed write operation. If you repeat your read request after a short time, the response should eventually return the more recent item». Практичний висновок для тесту звідти ж: сильна узгодженість буває опцією запиту, і перемкнути її часто дешевше, ніж накручувати . Один e2e-тест успадковує усіх ланок разом — детальніше причини й лікування розбирає глава Флакі-тести.

    Незрозуміло, хто винен. Тест «оформлення замовлення» впав. У якому з восьми сервісів? Червоний e2e показує лише, що ланцюг розірвався, але не де саме. Без трейсингу діагностика перетворюється на розкопки по логах кількох команд.

    Проблема середовища й версій. Щоб e2e був чесним, усі сервіси мають бути піднятими й у сумісних версіях одночасно. Але сервіс оплати вже викотив v5, а сервіс складу ще на v4 — на спільному стенді ти ловиш розсинхрон версій (version skew), якого в проді ніколи не буде. Термін галузевий, і означення в джерелі ширше за наше: це «a state of a distributed system in which it contains multiple incompatible versions of code, data, and/or configuration» — тобто розходитися можуть не лише версії сервісів, а й дані з конфігурацією. Наша теза там теж є, майже дослівно: стенд дає хибні позитиви — «problems that occur in a particular staged combination that wouldn't actually be spotted in production». Що джерело радить натомість: ловити розсинхрон у процесі просування release candidate середовищами, причому просувати разом із конфігурацією. Чиє це середовище, хто відповідає за його цілісність — окреме організаційне питання без простої відповіді.

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

    e2e-тест: оформити замовлення

    API Gateway

    Сервіс кошика

    Сервіс оплати

    Сервіс складу

    Черга подій

    Сервіс нотифікацій

    Пошта, стороннє API

    e2e-тест: оформити замовлення

    API Gateway

    Сервіс кошика

    Сервіс оплати

    Сервіс складу

    Черга подій

    Сервіс нотифікацій

    Пошта, стороннє API

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

    Сервіс в ізоляції vs інтеграційне середовище

    Між «протестувати один сервіс окремо» і «підняти все разом» лежить головний компроміс мікросервісного тестування.

    Сервіс в ізоляції — це коли ти піднімаєш один сервіс, а всіх його сусідів підміняєш дублерами (, mock-сервери на кшталт WireMock чи msw). Такий тест часто називають компонентним (component test): він перевіряє сервіс цілком, від його API до його бази, але без справжніх сусідів. Він швидкий, детермінований, повністю під твоїм контролем — ти сам вирішуєш, що відповість сервіс оплати, зокрема відтворюєш його помилки й таймаути, які на живому стенді не спровокуєш. Таксономію дублерів (stub, mock, fake, spy) розбирає окрема глава Тестові дублери.

    Ціна ізоляції: твій стаб — це твоє уявлення про сусіда, а не сам сусід. Якщо сервіс оплати змінив формат відповіді, а твій стаб — ні, компонентний тест лишається зеленим, тоді як у проді все зламано. Ізоляція не доводить, що сервіси реально домовляються.

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

    Сервіс в ізоляціїІнтеграційне середовище
    Що піднятоОдин сервіс + дублериРеальні сервіси разом
    ШвидкістьВисокаНизька
    ДетермінованістьВисока (ти керуєш дублерами)Низька (мережа, асинхронність)
    Що ловитьЛогіку сервісу, обробку помилок сусідівРеальні розбіжності інтеграції
    Чого не ловитьРозбіжність стаба з реальним сусідомТоне у флаку й вартості
    Хто «власник»Команда сервісуСпільний, розмитий

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

    Місце контрактних тестів

    Контрактний тест (contract testing) перевіряє, що двоє сервісів згодні щодо формату їхньої взаємодії — без того, щоб піднімати обидва одночасно. Контракт (contract) — це зафіксована спільна очікуваність: (consumer) заявляє, які поля й у якому вигляді він чекає від постачальника (provider), а постачальник окремо перевіряється проти цієї заяви.

    У підході consumer-driven contracts (контракти, керовані споживачем; інструмент-еталон — Pact) це працює так: тест споживача піднімає постачальника й водночас записує очікування у файл-контракт. Постачальник потім у своєму програє цей контракт проти себе справжнього й перевіряє, що досі його виконує. Обидва тести — швидкі й ізольовані, але разом вони дають гарантію, якої не дає жодна ізоляція окремо: що стаб споживача не розійшовся з реальним постачальником.

    Це і є та сама втрачена ланка. Контракт ловить те, що пропускає ізоляція (розсинхрон формату), не платячи ціною інтеграційного стенду. Механіку — schema drift, breaking vs non-breaking зміни, Pact Broker, валідацію проти OpenAPI як полегшений контракт — детально розбирає глава про в розділі API-тестування; тут важлива її роль у стратегії: контрактні тести замінюють значну частину інтеграційних e2e-перевірок «чи домовляються сервіси».

    Важлива межа: контракт перевіряє форму взаємодії (поля, типи, статуси), а не бізнес-результат наскрізного сценарію. Він скаже «сервіс оплати досі повертає поле status», але не скаже «замовлення справді оплатилося й склад його зарезервував». Тому контракти не витісняють тонкий шар e2e — вони прибирають потребу ганяти повний ланцюг лише заради перевірки сумісності API.

    Distributed tracing (оглядово)

    Коли інтеграційний чи e2e-тест таки впав, залишається найдорожче питання: у якому із сервісів розірвався ланцюг? Відповідь дає розподілений трейс (distributed tracing) — техніка, що зшиває один запит через усі сервіси в єдину картину.

    Ідея проста. На вході в систему запиту присвоюють унікальний ідентифікатор — trace id (або correlation id). Цей ідентифікатор передається далі з кожним міжсервісним викликом через спеціальний заголовок (стандарт W3C Trace Context визначає для цього заголовок traceparent). Кожен сервіс логує свій відрізок роботи як (span) з тим самим trace id. Зібравши всі span за одним trace id, система показує повний шлях запиту: через які сервіси пройшов, скільки часу провів у кожному, де саме впав чи затупив.

    Для QA це прямий інструмент, а не абстракція для SRE:

    • Локалізація падіння. Trace id з упалого тесту веде прямо до сервісу-винуватця замість ручного зіставлення логів кількох команд.
    • Атач до баг-репорту. Trace id у баг-репорті економить розробнику години — він одразу бачить повний шлях запиту.
    • Розуміння асинхронності. Трейс показує, де запит чекав на чергу чи зовнішнє API — часто це і є причина «плаваючого» таймауту в тесті.

    Найпоширеніший інструмент збору телеметрії — OpenTelemetry, і його офіційна дока прямо каже, що це «a Cloud Native Computing Foundation (CNCF) project», який виник зі злиття OpenTracing і OpenCensus. Слова «стандарт-де-факто» ми свідомо не вживаємо: такого статусу дока собі не присвоює. Дивляться трейси у застосунках на кшталт Jaeger чи Zipkin. Механіку роботи з логами й трейсингом під час діагностики докладніше розбирає окрема глава про у розділі про Git і CI/CD; performance-кут (APM, розподілений трейс під навантаженням) — у розділі про performance-тестування. Тобі як QA достатньо знати: якщо система інструментована трейсингом, вимагай доступу до нього — це найшвидший шлях від червоного тесту до винного сервісу.

    Стратегія покриття системи за рівнями

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

    E2E: кілька критичних наскрізних шляхів
    дорого, крихко, повільно

    Інтеграційні: сервіс + реальні сусіди
    реальні стики, обмежено

    Контрактні: чи домовляються сервіси
    дешево, детерміновано

    Компонентні: сервіс в ізоляції з дублерами
    швидко, логіка + обробка помилок

    Модульні: логіка всередині сервісу
    найдешевше

    E2E: кілька критичних наскрізних шляхів
    дорого, крихко, повільно

    Інтеграційні: сервіс + реальні сусіди
    реальні стики, обмежено

    Контрактні: чи домовляються сервіси
    дешево, детерміновано

    Компонентні: сервіс в ізоляції з дублерами
    швидко, логіка + обробка помилок

    Модульні: логіка всередині сервісу
    найдешевше

    Як розкласти покриття по рівнях:

    • Модульні — логіка всередині сервісу; відповідальність команди сервісу.
    • Компонентні — сервіс в ізоляції: його API, його база, обробка помилок і таймаутів сусідів (сусіди — дублери). Тут основна маса перевірок поведінки сервісу.
    • Контрактні — сумісність кожної пари «споживач ↔ постачальник». Замінюють більшість інтеграційних перевірок «чи збігаються формати».
    • Інтеграційні — реальні стики там, де контракт недостатній: асинхронні потоки через черги, складні багатокрокові взаємодії. Обмежено, бо дорого.
    • E2E — лише кілька найкритичніших наскрізних бізнес-шляхів (оформлення замовлення, реєстрація, оплата). Це довіри до системи в цілому, а не спосіб покрити всі гілки.

    Правило пріоритету при виборі рівня: перш ніж писати наскрізний тест, спитай — чи не ловиться цей ризик дешевше нижче? Розбіжність формату → контракт. Логіка обробки помилки сусіда → компонентний тест з дублером. І лише «чи справді весь бізнес-сценарій проходить наживо» → тонкий e2e. Ізоляцію даних для всього цього (окремий tenant, унікальні дані на прогін) розбирає глава Тест-дані — у розподіленій системі вона критична вдвічі, бо стан розмазаний по кількох базах.

    Типові помилки

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

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

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

    Виглядає як «мок сусіда = реальна поведінка сусіда», а насправді — мок повертає те, що ти в нього заклав. Він не знає про новий обов'язковий заголовок, зміну коду помилки чи новий формат дати. Мок — гіпотеза про сусіда, і без контракту ніхто не перевіряє, що гіпотеза досі правдива.

    Виглядає як «тест червоний — значить, баг у продукті», а насправді — у розподіленій системі червоний e2e найчастіше означає моргання середовища, розсинхрон версій на спільному стенді чи ще-не-доїхало-повідомлення. Спершу — трейс і перевірка версій сервісів, і лише потім висновок про баг.

    Підсумок

    • У мікросервісах ризик зміщується з логіки всередині сервісу на стики між сервісами — туди й має дивитися стратегія тестування.
    • Наскрізні e2e не викорінюють, а стоншують до кількох критичних бізнес-шляхів: 0.99^N доступності робить великий e2e-набір ненадійним.
    • Сервіс в ізоляції швидкий, але не бачить реальних розбіжностей; інтеграційне середовище бачить, але дороге — прогалину між ними закривають контрактні тести.
    • Контракт перевіряє форму взаємодії, а не бізнес-результат; він замінює інтеграційні перевірки сумісності, але не тонкий шар e2e.
    • Distributed tracing через єдиний trace id — головний інструмент QA для локалізації, у якому сервісі розірвався ланцюг.

    Можливі питання

    • «Чим тестування мікросервісів відрізняється від моноліту?» — інтерв'юер перевіряє, чи бачиш ти зсув ризику на мережеві стики, незалежні деплої й відсутність одного повного середовища, а не переказуєш означення.
    • «Чому не покрити все наскрізними тестами?» — чекають на крихкість (0.99^N), вартість, флак, розсинхрон версій і складність локалізації падіння; сильна відповідь одразу пропонує альтернативу — стоншити e2e й штовхнути перевірки нижче.
    • «Де місце контрактних тестів і чим вони кращі за інтеграційні?» — тут дивляться, чи розумієш ти, що контракт ловить розбіжність формату дешево й без спільного стенду, і чітко відділяєш «форму взаємодії» від «бізнес-результату».
    • «Тест оформлення замовлення впав — з чого почнеш?» — очікують систематику: перевірити, чи всі сервіси підняті й у сумісних версіях, узяти trace id і за трейсом знайти винний сервіс, і лише тоді вирішувати «баг продукту чи проблема середовища».
    • «Намалюй стратегію покриття для системи з N сервісів» — сильний кандидат малює рівні (модульні → компонентні → контрактні → інтеграційні → тонкий e2e) і обґрунтовує, що на якому рівні ловиться.

    Джерела

    Моноліт vs мікросервіси очима QA

    • Toby Clemson — Testing Strategies in a Microservice Architecture — у мікросервісній архітектурі компонент — це сам сервіс: «добре інкапсульована, звʼязна й незалежно замінна частина більшої системи» (trust: secondary, dec-0311).
    • ISTQB Glossary — канонічні означення тих самих слів, які тут означують інші обʼєкти: component — «A part of a system that can be tested in isolation», integration testing — «A test level that focuses on interactions between components or systems».

    Чому e2e в мікросервісній архітектурі болить

    • Toby Clemson — Testing Strategies in a Microservice Architecture — що саме дає наскрізний рівень у мікросервісах і за що йому платять: проміжки між сервісами й — фаєрволи, , балансувальники (trust: secondary, dec-0311).
    • Software Engineering at Google — Chapter 23: Continuous Integration — джерело поняття version skew («a state of a distributed system in which it contains multiple incompatible versions of code, data, and/or configuration») і класу хибних падінь спільного стенду — «problems that occur in a particular staged combination».

    Сервіс в ізоляції vs інтеграційне середовище

    • Toby Clemson — Testing Strategies in a Microservice Architecture — вигода ізоляції названа конкретно й не зводиться до швидкості: відтворюване відтворення помилкових сценаріїв; там же перелічені розвилки реалізації компонентного тесту — у процесі чи через мережу, дублери всередині сервісу чи назовні, справжнє сховище чи in-memory (trust: secondary, dec-0311).
    • Software Engineering at Google — Chapter 23: Continuous Integration — що джерело протиставляє спільному стенду: просування release candidate середовищами разом із конфігурацією, а не єдине «інтеграційне» оточення.

    Місце контрактних тестів

    • Toby Clemson — Testing Strategies in a Microservice Architecture — контрактний тест живе на межі зовнішнього сервісу, кожен споживач формує свій контракт, і контрактні тести не є компонентними — поведінку глибоко вони не перевіряють; при цьому контракт включає не лише структури даних, а й побічні ефекти та характеристики продуктивності (trust: secondary, dec-0311).
    • Pact — docs — інструментальна реалізація consumer-driven контрактів: очікування споживача фіксуються у виконуваному контракті, який верифікує постачальник.
    • ISTQB Glossary — канонічне означення: contract testing — «A type of integration testing that verifies that the interfaces are used as specified in their contracts».

    Distributed tracing (оглядово)

    • OpenTelemetry — Traces — механіка: трейс складається зі спанів зі спільним trace_id, вкладених через parent span ID, а розподіленим трейс робить поширення контексту.
    • OpenTelemetry — What is OpenTelemetry? — джерело обох фактів про проєкт: розкривається як Cloud Native Computing Foundation, а сам проєкт виник зі злиття OpenTracing і OpenCensus.
    • W3C — Trace Context — специфікація заголовка traceparent, яким контекст і передається між сервісами.
    • ISTQB® CTAL-TAE Syllabus v2.0 — бік автоматизації: унікальний ID взаємодії (correlation ID / trace ID) може логуватися рішенням автоматизації, щоб допомогти аналізувати результати.

    Стратегія покриття системи за рівнями

    • Toby Clemson — Testing Strategies in a Microservice Architecture — джерело самої розкладки рівнів (модульний, компонентний, інтеграційний, контрактний, наскрізний) і того, що розмір «юніта» строго не визначений, а соціальний і самотній стилі — не конкуренти (trust: secondary, dec-0311).
    • ISTQB Glossary — означення test pyramid як графічної моделі співвідношення обсягу тестування за рівнями, з якою цю розкладку звіряють.
    • AWS — DynamoDB read consistency — чому «ще не доїхало» це режим, а не баг: кінцева узгодженість є режимом читання за замовчуванням, а сильна вмикається опцією запиту.

    Типові помилки

    • Software Engineering at Google — Chapter 23: Continuous Integration — той самий діагноз у формулюванні джерела: false positives спільного стенду, і дельта, про яку зазвичай не думають, — сам створює розсинхрон, бо кілька версій живуть одночасно.
    • OpenTelemetry — Traces — інструмент першого кроку діагностики: спани зі спільним trace_id показують, де саме в ланцюгу зупинився запит.

    Пояснення

    «Поясни» працює з власним API-ключем Anthropic: запит іде з вашого браузера прямо до Anthropic.

    Свій API-ключ (BYOK)

    Вставте власний ключ Anthropic — пояснення працюватиме на реальній моделі. Ключ зберігається лише у вашому браузері (localStorage), нікуди не надсилається, крім api.anthropic.com, і не логується.