Складнощі тестування мікросервісів та що з ними робити

У червні 2019 року я виступивши на конференції ProQA.Today на тему тестування мікросервісів. Якщо коротко, то в моїй доповіді було чимало критики й могло скластися враження, що я затятий противник технології, альо це не так — удома я навіть маю свій Docker Registry на окремому сервері, з багатьма контейнерами для різних тестерських експериментів. А в Google Cloud у мене є власний застосунок. Як і в будь-якій технології, я бачу в мікросервісах сильні і слабкі сторони, де чимало залежить від правильної архітектури й способу використання. Кілька місяців я обмірковував свою доповідь, виступи інших спікерів та критику й готовий структурувати свої думки у статтю. Усі приклади нижче — мій досвід тестування монолітних систем і мікросервісів.

Точка відліку

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

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

Що мені не подобається в тестуванні мікросервісів

Мікросервіси складно тестувати атомарно . У 99% випадків не можна запустити один окремий мікросервіс, щоб протестувати його REST веб-сервіси, наприклад, не створивши спочатку мок усіх пов'язаних з ним сервісів. Якщо у команди розробки немає часу робити моки, можна забути про інтеграційне тестування на умовному Jenkins'і у вакуумі. Вже тут мене запитують: а як же тестування контрактів? Як я розумію, тестування контрактів лише перевіряє відповідність веб-сервісів специфікації (swagger чи щось подібне), а мене цікавить саме тестування веб-сервісів з визначеними сетами даних. З монолітами зазвичай простіше — хоч локально розгорни собі систему й тестуй.

Для мікросервісів важко керувати даними . Кожен мікросервіс може мати свою окрему базу даних, ніяк не пов'язану з базами інших мікросервісів. З погляду автономності це, звісно, правильно, але є одне «але»: якщо сутності (фрази об'єкти) у системі, що ми тестуємо, складаються з менших сутностей шкірного мікросервісу (як Вольтрон ), база даних не може забезпечити їхньої цілісності. Для зв'язку язності даних у кожному мікросервісі потрібно створювати окремий механізм, наприклад, зберігати ID сутностей інших сервісів. Проблема в тому, що дані одного мікросервісу можна стерти ї помилку можна знайте лише на етапі тестування (або в продакшені). А під час створення/заміни даних потрібно є ятовувати ID одних об'єктів, щоб потім зберегти їх у базах інших мікросервісів. Єдиним робочим підходом нашої команди стало створення й редагування даних через UI в невеликих обсягах під кожний конкретний тест — так довше, ніж прямо через БД, але простіше.

Для мікросервісів важко забезпечити транзакційність . Тут як у поганому анекдоті: «Студент, щоб скласти сесію, давши хабаря викладачеві, потім секретарю декана, а потім декан хабаря не взявши. Шкода, що хабарі не транзакційні й перші двоє грошей не повернути». Тестування транзакцій у мікросервісах подібне до цього жарту: перший сервіс робить зміни в даних і передає їх іншій сервісу — другий сервіс робить зміни у своїх даних і передає третьому, і тут трапляється помилка! Не просто помилка, а наприклад, необроблений Null Pointer Exception. Тест не пройшов, дані перших двох мікросервісів не відповідають іншим — потрібно вручну їх чистити. Ба більше, для забезпечення транзакційності між різними мікросервісами розробникам треба писати більше кодом, більше веб-сервісів для зворотного зв'язку (що потребує більше часу на тестування). А найгірше те, що з погляду цілої системи може бути зовсім не очевидно, в якому саме мікросервісі сталася помилка — потрібно брати логи й дані всіх залучених сервісів і перевіряти. У монолітах транзакційність часто гарантують на рівні БД.

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

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

Для системи мікросервісів важко зробити автоматизоване UI-тестування . Насправді розробити самі автотести легко, і тієї ж Selenium WebDriver добре виконує свою роботу. Проблеми з'єднання є, коли ми намагаємося додати автотести в CI/CD. З монолітом усе просто:

  1. Новий білд.
  2. Автотести.
  3. ???
  4. Profit.

Складнощі починаються, коли система складається з 2+ мікросервісів:

Q : Де виконувати регресійні тести?
A : На умовному Jenkins потрібно створити велику кількість моків.
Q : Може, після деплою?
А : А якщо під час виконання тесту почнеться деплой іншого мікросервісу?
Q : Ставити всі білди в чергу?
А : А якщо черга стані завелика?

Кожне запитання породжує низку інших запитань. У результаті найдієвішим компромісом для нашої команди обрано щонічне виконання тестів в окремому середовищі лише для автотестів (щоб контролювати дані) і мануальне регулювання деплою в це окреме середовище.

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

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

Кожен веб-сервіс одночасно повинний передавати більше даних, бо буває, що перший мікросервіс передає дані іншого, тієї використовує два умовні поля для своїх обрахунків (наприклад, лише перевіряє ID), а всі інші дані передає наступному сервісу, якому вони корисні (на малюнку схема передачі корисного навантаження payload між мікросервісами. Поля, що використовує сервіс — підсвічено).

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

У мікросервісах важче знайті першопричину помилки . Базова інструкція тестувальника вімагає, що в разі знаходження бага потрібно його відтворити й зарепортити з усіма передумовами, кроками, сподіваними результатами тощо. Однак кожний тестувальник знає, що в разі складних систем для успішного фіксу, краще знайті першопричину бага, додати логи й дані, щоб не отримати cannot reproduce. І в мікросервісах це може стати справжньою проблемою, якщо одна дія користувача спричинює ланцюгову реакцію, де залучено чимало систем. У якому сервісі помилка?

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

Мікросервіси потребують чимало ресурсів . Я пам пам'ятаю ті часи, коли мав низку невеликих тестових серверів на Tomcat з кількома гігабайтами ОПЕРАТИВНОЇ пам'яті, у які міг сам задеплоїти нову версію продукту й почати тестування. Навіть робити редеплой попередніх версій, щоб визначити, коли саме з'єднання явилася помилка. Нині середовище тестування системи мікросервісів, в якому я працюю, потребує 64 ГБ. А іноді й цього замало! Я не сказавши бі, що інфраструктура — це безпосередня проблема тест-ліда, альо коли в системі ліміт ресурсів, то це може спричинити баги й неможливість вчасно додати ще один контейнер з тестами. Кожен контейнер мікросервісу — власна віртуальна машина, що використовує ресурси, у разі ж моноліту (який теж можна запакувати в докер за потреби) — у нас є лише один сервер чи віртуальна машина.

Висновки

Тепер спробую позитивно підсумувати все вище написане :) Я не закликаю відмовлятися від мікросервісів — лише буті готуємо до складнощів, які ця технологія може спричинити, зокрема:

Дякую, що дочитали. Напишіть про ваш досвід, мені справді цікаво знаті success story :)

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

Опубліковано: 12/09/19 @ 10:00
Розділ Різне

Рекомендуємо:

Як ми впровадили Scrum: граблі і точки зростання
Виведення сайту по монтажу натяжних стель в топ 3
Консервація проблем замість реформ. Що не так з ініціативою Кабміну
C++ дайджест #19: підготовка до співбесід
«Це невідворотна еволюція суспільства». Чому нам не оминути нових податків та куди вони підуть