AQA вебаплікації на мікросервісній архітектурі
Вітаю! Я Євген Гармаш, Automation QA Engineer у компанії Vector Software. У цій статті я поділюсь своїм досвідом автоматизації тестування вебаплікації, яка побудована на мікросервісній архітектурі, та продемонструю приклад побудови багаторівневого тестування.
Цей матеріал буде корисний ІТ-фахівцям, які хочуть опанувати тестування мікросервісів, переходячи з монолітних архітектур на більш гнучкі та масштабовані рішення, навіть якщо вони ще не мають великого досвіду в цій сфері.
До нас звернувся клієнт із запитом перевести монолітну вебаплікацію на мікросервісну. Це було зумовлено необхідністю адаптувати аплікацію під різні конфігурації при розгортанні, щоб можна було включати або виключати певні функції відповідно до вимог клієнтів, залежно від їхньої локалізації.
Фактично, аплікація дозволяла користувачам купувати та продавати активи, обмінюватися повідомленнями та виконувати інші стандартні функції. Клієнт перевіряв свою аплікацію за допомогою набору Е2Е-тестів. Як тестове середовище, він використовував тестовий Jenkins-агент, де розгорталася аплікація, а тести виконувались в окремому Jenkins-пайплайні.
З часом команда розробників почала переписувати аплікацію, і, в результаті, колишній моноліт став набором мікросервісів.
Архітектура мікросервісної аплікації
- Front-end отримує команди від користувачів, такі як реєстрація нового користувача, покупка активів і відправлення повідомлень, і відправляє відповідні запити до мікросервісів B, C та D.
- Microservice A — головний сервіс, який працює як центральний репозиторій конфігураційних даних та зберігає конфігураційні дані в оперативній пам’яті для швидкого доступу.
- Microservice B відповідає за придбання активів користувачами та зберігання інформації про ці активи в базі даних PostgreSQL. Він також отримує конфігураційні дані від Microservice A для налаштування своєї роботи. Крім того, він спілкується з Microservice D через Apache Kafka для синхронізації даних.
- Microservice С відповідає за відправлення приватних повідомлень між користувачами. Він також отримує конфігураційні дані від Microservice A і спілкується з Microservice D через Apache Kafka для синхронізації повідомлень/користувачів.
- Microservice D — це сервіс, який керує користувачами та зберігає всі їх дані в базі даних.
Тестування
Завданням QA-команди було забезпечити тестування аплікації. Однак, під час роботи з наявними E2E-сценаріями виникли проблеми, які потрібно було вирішити.
Неможливість раннього тестування. E2E-тести неефективні для раннього тестування мікросервісів, особливо коли деякі з них ще перебувають у процесі міграції, і не можна розгорнути всю аплікацію для їх виконання.
Тривалість виконання. Виконання E2E-тестів займало понад годину, що сповільнювало процес розробки.
Складність локалізації джерела дефекту. E2E-тести перевіряли всю систему як одне ціле, тому важко було знайти джерело проблеми.
Наприклад, якщо мікросервіси використовують API один одного, проблеми в спільному інтерфейсі складно виявити, оскільки E2E-тести перевіряють ширші сценарії взаємодії, а не окремі запити. Також, при використанні Apache Kafka немає гарантії доставки повідомлень, що ускладнює виявлення проблем.
Управління тестовими даними. В мікросервісній архітектурі виникають проблеми з управлінням тестовими даними через використання різних баз даних (Microservice B + PostgreSQL, Microservice D + SQL).
Наприклад, при паралельному запуску E2E-тестів, які модифікують конфігурацію у базі даних, може виникнути конфлікт стану між тестами, що спричиняє непередбачувані результати через одночасні зміни.
Залежність від інших компонентів. Використання Apache Kafka робить тести менш передбачуваними через асинхронний характер обробки повідомлень. Це може призвести до каскадного збою всього тестового набору, що ускладнює тестування цільових функцій.
Рішення
Щоб розв’язати ці проблеми, QA-команда вирішила розділити тестування на різні рівні. Це дозволило нам почати тестувати якнайшвидше, навіть коли мікросервіси переписувалися поетапно.
Ось приклад такої піраміди тестування:
1. Unit testing
Команда розробників Front-end та Back-end написали необхідну кількість Unit-тестів для покриття коду. Також був інтегрований SonarQube до CI/CD, щоб аналізувати якість коду. Unit-тести запускалися на Pipeline перед злиттям у головну гілку. Це важливо, оскільки Unit-тести є найнижчим рівнем в піраміді тестування і забезпечують стабільність коду.
2. Component testing
Наступним кроком є компонентне тестування мікросервісів у ізоляції, що займає другий рівень піраміди тестування. Це простий, швидкий та надійний спосіб перевірити кожен мікросервіс окремо.
Ми використовували Docker для налаштування середовища, де кожен компонент системи, включно з тестовими інструментами, був розгорнутий у власному Linux-контейнері.
Оскільки наша аплікація взаємодіє з іншими мікросервісами та базами даних, ми використали WireMock.Net для створення віртуальних мікросервісів та In-Memory Database Provider для імітації бази даних, забезпечуючи таким чином повну ізоляцію і відтворення необхідних залежностей без потреби у реальних зовнішніх сервісах. У рамках процесу неперервної інтеграції (CI), Component-тести виконувались одразу після Unit-тестів для кожного сервісу.
Переваги:
- виявлення помилок на ранньому етапі;
- ізоляція тестових середовищ;
- швидкість виконання тестів.
3. Integration testing
Ключовим аспектом тестування інтеграції в мікросервісній архітектурі є перевірка правильної взаємодії між різними компонентами системи, такими як служби програми, бази даних і зовнішні сервіси.
Інтеграційне тестування розглядається як третій рівень піраміди тестування через його важливість у забезпеченні правильної взаємодії між компонентами, які функціонують разом належним чином, без збоїв у їх взаємодії.
Припустимо, ми хочемо перевірити, як Microservice B взаємодіє з базою даних.
Для цього ми створюємо тестове середовище на базі Linux-контейнерів, де ми можемо розгорнути сам сервіс, базу даних, Kafka і тестову систему.
Тестова система включає ряд тестів, які перевіряють цю взаємодію, а також використовує Mock Service (WireMock.Net), щоб імітувати поведінку мікросервісу A.
Такий процес інтеграційного тестування виконується у контексті неперервної інтеграції (CI), зазвичай відразу після проведення компонентних тестів, і ми його застосовували для перевірки всіх мікросервісів у нашій системі.
Порядок виконання тестування:
- Підготовка передумов. Інтеграційний тест створює необхідні тестові дані в базі даних PostgreSQL.
- Відправка запиту до Microservice B. Тест відправляє HTTP-запит до Microservice B для створення активу.
- Отримання конфігурації від Microservice A. Microservice B відправляє запит до Microservice A для отримання необхідної конфігурації, і Mock Service у Microservice A перехоплює цей запит та повертає заздалегідь підготовлену конфігурацію для тесту.
- Створення сутності в БД. Microservice B виконує запит в базу даних для створення сутності
- Надсилання повідомлення до Kafka. Microservice B відправляє повідомлення до Kafka, де воно зберігається.
- Валідація результатів. Проводиться перевірка, чи була сутність успішно створена в базі даних та чи було успішно доставлено повідомлення до Kafka.
Вище вказаний підхід має наступні переваги:
- швидкий зворотний зв’язок;
- ізоляція середовищ;
- швидкість розгортання тестового середовища;
- заглушки комунікацій з іншими сервісами.
4. Contract testing
Після завершення інтеграційного тестування необхідно перевірити, як мікросервіси взаємодіють та передають дані між собою.
Наступним кроком у піраміді тестування є застосування контрактного тестування. Цей тип тестів дозволяє гарантувати коректність взаємодії та дотримання домовлених інтерфейсів і угод між сервісами.
Згідно з архітектурою, в нашій аплікації є два способи обміну даними:
- Використання REST API.
- Використання повідомлень через Apache Kafka.
Контрактне тестування з використанням REST
Розглянемо комунікацію між Micreservice B та Micreservice A:
- Спочатку ми створюємо Docker-образи для тестування та сервісів на базі Linux.
- Pact Broker Server, який зберігає та керує контрактами, встановлюється на Jenkins-агенті.
- Для генерації контрактів використовуємо фреймворк Pact.net.
В цьому випадку ми використовуємо нестандартний підхід до контрактного тестування, де контракт генерується реальним мікросервісом.
Consumer Microservice B:
- Виклик Pact Builder: Consumer test викликає Pact Builder, в якому явно вказуємо, який HTTP запит треба перехопити (Endpoint, Http Method, Query) та відповідь, яку очікуємо від Microservice A.
- Надсилання HTTP запиту: Consumer test відправляє HTTP запит до Microservice B.
- Перехоплення HTTP запиту: Microservice B відправляє HTTP запит який перехоплюється Pact у випадку, якщо цей запит співпадає з раніше вказаним запитом в пункті 1.
- Валідація та публікація контракту: У випадку успішної валідації, тест публікує контракт в Pact Broker Server.
Provider Microservice A:
- Виклик Pact Verifier: Provider test викликає Pact Verifier, де вказується URL Pact Broker сервера та необхідну назву Provider в контракті згенерованому Consumer.
- Отримання контракту: Pact Verifier через HTTP запит отримує необхідний контракт.
- Надсилання HTTP запиту: Pact відправляє вказаний Consumer запит до Microservice A.
- Обробка запиту: Після отримання HTTP запиту, Microservice A повертає відповідь до Pact Verifier.
- Валідація та публікація результату: Pact перевіряє отриману відповідь від Microservice A з відповіддю вказаною в контракті Consumer та відправляє результат перевірки до Pact Broker Server.
Контрактне тестування з використанням повідомлень
Ми провели тестування між Microservice B та Microservice D.
- Використання Pact.net. Для реалізації контрактних тестів використовувався Pact.net, який забезпечує можливість тестування інтеграції сервісів через обмін повідомленнями.
- Microservice B. Тестове рішення для Microservice B було реалізоване в Linux-контейнері. Це рішення відповідає за створення повідомлень контракту та їх публікацію до Pact Broker Server.
- Microservice D. Для Microservice D також було розгорнуто тестове рішення в Linux-контейнері. Крім цього, в Linux-контейнері була піднята Kafka.
- Мета тестового рішення. Основною метою тестового рішення було завантаження контракту та публікація повідомлень у Kafka.
- Послідовність виконання тестів. У поточному проєкті контрактні тести виконувалися після виконання інтеграційних тестів в рамках CI (Continuous Integration).
Consumer Microservice B:
- Виклик Pact Builder. Consumer Test викликає Pact Builder для створення контракту повідомлення для Microservice D.
- Публікація контракту. Pact Builder за допомогою HTTP публікує контракт в Pact Broker Server.
Provider Microservice D:
- Виклик Pact Verifier. Provider Test викликає Pact Verifier, який містить конфігураційні дані Pact Broker-сервера.
- Отримання контракту. Pact Verifier через HTTP запит отримує необхідний контракт з повідомленнями.
- Виклик Kafka Producer. Provider Test викликає Kafka Producer, який відповідає за відправку повідомлень в Kafka.
- Надсилання повідомлень в Kafka. Kafka Producer відправляє в Kafka повідомлення, вказане в контракті.
- Отримання повідомлень. Kafka Listener, якщо підписаний на певний topic, отримує повідомлення.
- Валідація повідомлень. Pact Verifier відправляє отримане повідомлення для перевірки з очікуваним результатом.
- Публікація результатів. Pact Verifier відправляє результати перевірки до Pact Broker серверу.
Переваги:
- забезпечення сумісності між сервісами;
- швидке виявлення змін;
- зменшення залежностей між сервісами;
- швидкість розгортання тестового середовища.
5. E2E testing
Навіть з багаторівневою пірамідою тестування, Е2Е-тестування залишається важливим кроком у тестуванні мікросервісних аплікацій. Після впровадження багаторівневої піраміди ми зменшили кількість Е2Е-тестів, що призвело до таких переваг:
- Зменшення тривалості виконання тестів. Зменшення кількості E2E-тестів призводить до швидшого зворотного зв’язку і забезпечення відповідності цілісної системи вимогам замовника.
- Зниження затрат на підтримку тестів. Е2Е-тести потребують багато часу на виконання, перевірку та виправлення. Під час оновлення функціональності або структури системи їх доводиться переглядати та змінювати, що потребує додаткових ресурсів.
- Зменшення затрат на підтримку тестового середовища. Кожна унікальна конфігурація аплікації може вимагати окремого тестового середовища, налаштованого під специфічні вимоги та залежності. Тому для розгортання, налаштування та управління такими середовищами потрібно значно більше ресурсів і часу. Скорочуючи обсяг E2E-тестів і фокусуючись на більш низькорівневих тестах, можливо оптимізувати використання тестових середовищ, зробити їх більш універсальними та зменшити час та ресурси, необхідні для їх підготовки та підтримки.
Висновок
Тестування мікросервісів є важливим для забезпечення якості та надійності вебаплікацій. Через розподілену природу мікросервісної архітектури, процес тестування вимагає комплексного підходу.
Ми використовуємо багаторівневу піраміду тестування, щоб забезпечити стабільність та високу якість системи. Це дозволяє нам контролювати ризики на різних етапах розробки та впровадження програмного продукту.
Переваги:
- Багаторівневий підхід. Тестування на різних рівнях гарантує, що кожна частина системи працює належним чином.
- Ізоляція компонентів. Тестування мікросервісів окремо допомагає точно ідентифікувати та розв’язувати проблеми без впливу на інші частини системи.
- Швидкість та ефективність. Автоматизація тестування, включаючи використання Docker, сприяє швидкому налаштуванню тестових середовищ і прискорює процес розробки та впровадження змін.
- Гнучкість тестування. Можливість використовувати заглушки для зовнішніх залежностей і сервісів дозволяє тестувати цільовий сервіс, незалежно від поточного стану розробки або доступності сервісів, з якими він комунікує.
Недоліки:
- Складність управління. Управління багаторівневим процесом тестування може бути важким, особливо у великих системах з багатьма мікросервісами, і може потребувати додаткових зусиль для координації та аналізу результатів.
- Комплексність налаштування. Налаштування Docker-контейнерів для кожного мікросервісу та конфігурація Kafka можуть бути складними та часозатратними, особливо для великих і складних систем.
- Необхідність спеціалізованих знань. Ефективне використання технологій, таких як Docker, Jenkins та PactNet, вимагає певного рівня технічної експертизи та досвіду. Це означає, що команді тестувальників доведеться пройти навчання, щоб опанувати ці інструменти та інтегрувати їх у процес тестування, що може збільшити час та витрати на підготовку персоналу.
7 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів