Тест-дизайн у Back-end тестуванні: досвід розробників Wirex R&D

Усі статті, обговорення, новини про тестування — в одному місці. Підписуйтеся на QA DOU!

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

Для контексту: продукт Wirex надає широкий спектр фiнансових операцiй з крипто- та фiатними валютами, таких як обмiн, внутрiшнi та зовнiшнi транзакції, депозити, картковi транзакцiї з Visa й MasterCard та багато iншого. Оскільки цей ринок динамічний, а продукт стрiмко розвивається та росте, з кожним роком складність тестування зростає. Для інженерів це означає стабільно високий темп розробки, динамічність змін, але без порушення процесів і пiдходів до тестування.

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

Саме тому на прикладі власних кейсів, мені б хотілося висвітлити деяку проблематику в цьому напрямку професії тестувальника, оскільки у Wirex моя команда займається тестуванням Back-end платформи, яка базується на мікросервісній архітектурі. Тестування передбачає перевірки на різних рівнях: модульне, а також інтеграційне тестування, залежно від його етапу та цілей. Це може бути один мікросервіс і його робота в ізольованому середовищі, або ж ланцюжок мікросервісів і їхня взаємодія у зв’язці. Залежно від цього, фокус доводиться зміщувати, іноді кардинально, при використанні однієї й тієї ж техніки тест-дизайну. Тож на що слід звертати увагу при подібних завданнях і чому?

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

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

Наприклад, маємо кілька валют:

  • MATIC транспорт 1 (Polygon)
  • ETH транспорт 2 (Ethereum)
  • AVAX транспорт 3 (AVAX)
  • USDC, який може мати одночасно 3 транспорти:
    • транспорт 1
    • транспорт 2
    • транспорт 3

Для отримання депозиту на акаунт у цій монеті потрібна адреса.

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

  • Matic deposit перевірено транспорт 1
  • Eth deposit перевірено транспорт 2
  • AVAX deposit перевірено транспорт 3

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

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

  • MATIC tr1
  • ETH tr2
  • AVAX tr3
  • Або так:
  • MATIC tr1
  • USDC tr2
  • AVAX tr3

Зробивши ирм перевірки, ми переконалися, що:

  • Транзакція надійшла до системи через потрібний транспорт.
  • Логіка матчингу токен + адреса відпрацювала коректно.

Немає принципового значення, який токен буде ключовим, якщо у них однакові адреси й ми перевіряємо всі три типи транспорту. З цієї точки зору, згідно з наявним аналізом даних на цей момент, такий набір тестів оптимальний, перевірка всіх чотирьох валют призведе до дублювання депозиту за якомось з транспортів, а це немалі витрати часу. Оскільки принципи в тестовій мережі багато в чому аналогічні до принципів роботи блокчейну, потрібно буде дочекатися підтверджень нашої транзакції з мережі блокчейн, на N-ному підтвердженні транзакція буде отримана нашою системою і пройде подальша її обробка — в кращому випадку, при кожній тестовій ітерації ми втрачатимемо від 3 до 5 хвилин.

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

У тестуванні Back-end, на мою думку, все ж основний акцент слід робити на технічних нюансах та розумінні системи. У прикладі вище, застосування якоїсь техніки тест-дизайну та сліпе їй слідування призвело б до критичної помилки. За час мого досвіду в IT-індустрії таке траплялося часто: коли люди роблять наголос у бік сильного розуміння теорії тестування (яка необхідна, звісно), але знання стандартів якості роботи двигуна мало в чому допоможе, якщо ми не в змозі відрізнити колінвал від поршня циліндра. Так і в нашому випадку, додавши лише одну «надлишкову» з певної точки зору перевірку, ми виявили б досить критичний баг. Багато хто з вас, спираючись навіть на професійну інтуїцію, вже, певно, здогадався, про яку перевірку йдеться. Але без належної підготовки та бекграунду включити цю перевірку на стадії аналізу було б складно.

Якщо ми трохи знайомі з проєктом і його кодовою реалізацією, на стадії аналізу можемо як мінімум поцікавитися, яким чином відбувається матчинг з точки зору коду. Згодом, ми не припустилися б банальної помилки, до якої призвела зміна і розширення логіки роботи з транзакціями, адже раніше в системі не було мультитранспорту для деяких валют, а від того і застосування звичайного SingleOrDefault() при поверненні адреси з методу не повертало помилку. Насправді, раніше він міг бути тільки один, невеликий нюанс з точки зору розробки: не використавши FirstOrDefault() та не перевіривши у наборах депозиту на валюту, у якої вже замовлені реквізити для всіх трьох типів трансферу, ми б обірвали досить важливий реліз.

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

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

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

Ефективність тієї чи іншої техніки тест-дизайну завжди залежить від знання системи та її прихованої логіки. Це особливо помітно при тестуванні саме Back-end. Більше того, при різному рівні розуміння системи, класи можуть узагальнювати абсолютно різні об’єкти.

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

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

Як ми припустили вище, є три типи транспорту, а депозит здійснюється через транспорт 2. У такому разі наш сервіс обробляє транзакцію, робить запит на зміну балансу та кидає івент про успішну обробку транзакції. Цей івент підхоплюють інші мікросервіси та виконують свою частину роботи. З точки зору компонентного тестування, всі три типи транспорту для нас — один клас еквівалентності, адже для WalletManager’а це лише string або enum.

TransactionPending/Processed{Args: TransportType: Transport1, WalletId: qwertg34-ertg-124g-bg5d-efrt5ghy98dn1, Status: Processed}

Але на рівні інтеграційного тестування кожен з транспортів може бути окремим класом, наприклад: івент TransactionPending слухає ще два сервіси (BalanceOperator і деякий сервіс А); у випадку, якщо ми отримаємо Transport1, сервіс BalanceOperator робить запис в базі даних та кидає івент про зміну балансу. Так, по ланцюжку транзакція завершується успішним відображенням нотифікації та балансу у користувача. Якщо ж транзакція була отримана через Transport2, BalanceOperator виконує вже іншу роботу, перед тим як кинути івент про зміну балансу та зробити запис в базі даних, він чекає івента Validated від сервісу B. Щоб отримати цей івент, необхідно щоб iнший сервіс А так само відреагував на TransactionPending і сформував розширену модель об’єкту транзакції та прокинув її на сервic B, який служить чимось накшталт гейтвея для 3rdParty: по закінченню очікування певного статусу про валідацію вiд 3rdParty він сповістить систему івентом Validated.

Приклад може здатися заплутаним, але в Back-end тестуванні це — звичайний робочий флоу тестування або аналізу роботи фічі. Як бачимо, набір тестів нам знадобиться зовсім різний, та й сам аналіз у тому чи іншому разі оперуватиме абсолютно різною кількістю даних та інформації. У подібних випадках, ще до стадії тест-дизайну, ми повинні розуміти ключові точки взаємодії системи та їхні особливості, а також приймати рішення, на якому рівні необхідно здійснювати перевірки та наскільки надмірним на тих рівнях має бути покриття. Варто аналізувати, чи не будуть перевірки повторними або надлишковими, чи важливо змістити фокус у бік конкретного компонента та його внутрішньої логіки роботи, або ж для нас достатньо у процесі інтеграційного тестування переконатися в тому, що при отриманні вхідних параметрів А ми отримуємо очікувану відповідь B.

У Back-end тестуванні завжди важливо перевіряти не тільки успішну роботу методу за допомогою Request-Response, адже це лише свідчить про те, що метод повернув очікувані значення, отримавши коректні параметри. Перевіряти часом важливо й саму логіку виконання цього методу, коли ми робимо запит звертаючись до якогось сервісу, той виконує низку викликів інших методів, зокрема його внутрішні методи, які взагалі можуть не мати якогось API чи методи стороннього сервісу, через його клієнта.

Сподобалась стаття? Натискай «Подобається» внизу. Це допоможе автору виграти подарунок у програмі #ПишуНаDOU

👍ПодобаєтьсяСподобалось8
До обраногоВ обраному3
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Підписатись на коментарі