Безперервне автоматичне тестування в DevOps-циклі — як ми запустили це на проєкті
Привіт! Мене звати Володимир Шелогуров, і я — тестувальник. З восьми років, що працюю в MobiDev, більшість часу був дотичний до автоматизації, але тільки згодом отримав проєкт, де автотести стали моїм основним профілем.
Це перший раз, коли у нас в реальному житті зібралась вся славнозвісна пірамідка тестування, починаючи від Unit тестів. У цій піраміді я створив API-тести та мобільні e2e-тести для кросплатформового додатку, інтегрував їх в Azure DevOps, та познайомився з DevOps-концепціями та практиками. Деякі рішення та підходи, до яких ми прийшли, були нетиповими для світу автоматизації, але чудово працювали та приносили користь.
Ця стаття — продукт аналізу мого досвіду, де я намагався розкласти все по поличках та сформувати головні висновки. Вони можуть бути цікавими й корисними, перш за все тестувальникам, автоматизаторам, а також всім, хто хоче більше дізнатись про DevOps та автоматизацію тестування.
DevOps як поєднання діяльності з розробки, забезпечення якості та операційних процесів

Розробка програмного забезпечення в контексті DevOps — це не лише кодування, це також проєктування, специфікації та вимоги. QA включає всі процеси тестування та дії для забезпечення якості продукції.
Сторона операцій (процесів) не менш важлива. Традиційно команди Agile прагнули швидко створювати програмне забезпечення, але інфраструктура не була достатньо швидкою. Ви могли швидко написати код і швидше його протестувати, але процеси збірки та доставки сповільнювали темп, — і саме з’явилась спеціальність та окремий напрям DevOps.
DevOps по суті об’єднує ці три діяльності у так званий нескінченний цикл розробки:

У цьому циклі ми беремо ідею та перетворюємо її на кінцевий продукт. Коли нова фіча випускається, ми дивимось як нею користуються, і на основі цієї інформації плануємо наш наступний цикл. Тому він нескінченний.
Якщо уважно подивитись на цю діаграму, може виникнути питання...
А де ж тестування в цій схемі
Можливо воно на етапі верифікації? Або на етапі планування — ми ж маємо тестувати раніше, перевіряти специфікації, щоб попередити баги ще до того, як вони з’явились? Чи може тестування десь між упаковкою та релізом? Але відповідь дещо складніша. Безперервне тестування включає перевірки на всіх етапах!

Тож подивимось глибше. Безперервне тестування починається з перевірки різноманітних вимог та юзер сторі. Далі — тестування початкових функцій у процесі їх розробки, тестування під час інтеграції з іншими частинами програмного забезпечення.
Потім, регресія — повторне тестування перед релізом, та знов тестування, після релізу. І моніторинг вже робочого продукту, що також є формою тестування. Отримавши результати на одному етапі, ми можемо покращити наступний.
Отже, безперервне тестування є впродовж всього циклу. Але це не означає, що треба скрізь робити ті самі види тестів. Тестування, яке ви проводите на початку під час планування та створення, не буде таким самим, як пізніше, коли будете перевіряти продукт перед релізом.
Отже, перш ніж перейти до реального прикладу, подивімося на
Деякі поширені очікування щодо безперервного тестування в DevOps
Люди хочуть DevOps і автоматизацію тестування, щоб пришвидшити розробку. Проблема з цією концепцією в тому, що коли ви робите щось швидше, це не обов’язково означає кращу якість. Ми не хочемо швидше релізити низькоякісний код. Уявіть, що ви натискаєте кнопку — код надсилається в репозиторій, запускаються автоматизовані тести, не виявляється жодних проблем, збирається білд та відправляється в продакшн. При цьому може бути менше шансів реально оцінити та усунути будь-який ризик.
Наприклад, можуть бути баги, що не знаходяться сценаріями швидких та ранніх автотестів. Не приділяючи належної уваги безперервному тестуванню, ви можете отримати хаос замість переваг.
Люди хочуть більш раннього запуску автотестів. Щоб швидше запускати автоматизовані тести, їх слід запускати якомога раніше в конвеєрі DevOps. Це концепція «Shift Left». Для досягнення швидкості та стабільності тести роблять таким чином, щоб уникнути надто складних сценаріїв і ретельних перевірок.
Але що, як нам знадобиться автоматизація для більш поглибленого тестування пізніше в циклі розробки? Ідея безперервного тестування охоплює всі етапи, від «Ліворуч» до «Праворуч», відповідно, має бути місце автоматизації тестування і на пізніших етапах.
Як ми реалізували безперервне тестування на нашому проєкті
Тестування на ранній стадії
Говорячи про зсув ліворуч, найкращі практики пропонують тестування ще до внесення будь-яких змін у код. Це включає тестування дизайну та вимог, з одного боку, і Test-Driven Development (розробку, керовану тестуванням) — з іншого.
Робота з вимогами
Щоб створити ефективні специфікації, усі члени команди повинні співпрацювати. Інженери з контролю якості зазвичай є голосом користувача, оскільки вони розуміють, що має робити система та яку цінність вона має принести.
Тому наші розробники і тестувальники працюють разом над аналізом вимог. Це дає нам міцний фундамент для подальшої співпраці.
Юніт-тести
Саме з них починається автоматизоване тестування. Після аналізу вимог, наші розробники починають втілювати їх у життя. І перш ніж писати код, пишуться тести що будуть його перевіряти. Усі ці тести спочатку будуть фейлитись, і треба так написати програму, щоб зрештою тести пройшли. Далі ці тести лишаються, як гарантія того, що код працюватиме після подальших змін.
API-тести
Тестування API є важливим, оскільки якщо ви розробляєте API, інші частини системи залежатимуть від цього. І якщо ви зламаєте свій API, ви зламаєте й інші частини застосунку чи його екосистеми.
Автоматизацію API загалом легше розробляти та підтримувати, оскільки API змінюється не так часто, як інтерфейс користувача. Вона також працює швидше і її легше інтегрувати в CI/CD. Наш технологічний стек для цієї частини включав NodeJS, Mocha, Chai та Axios як бібліотеку запитів (проте в інших проєктах ми віддаємо перевагу Fetch). Виконання тестів, які охоплюють усі наші серверні ендпоінти, дає нам впевненість, що на базовому рівні програма працюватиме.
Крім того, ми розробили кілька інструментів для прискорення мануального тестування на основі нашої основної автоматизації API. Наприклад, складна реєстрація нових юзерів, та перевірка цілісності контенту користувачів. Деякі складні сценарії, виконання яких вручну займало кілька днів, було виконано за лічені хвилини та з більшою точністю. Це була перша видима й об’єктивна вигода від автоматизації тестування на нашому проєкті.
Інтеграція в CI/CD
Усі наші процеси були інтегровані в Azure DevOps CI/CD. Щойно автоматизація API була готова, ми інтегрували її в Pipeline збірки білдів, щоб перевірки завжди запускалися перед створенням нової тестової версії застосунку.
Тестування E2e
Як тільки ми виконали всі попередні кроки, ми почали автоматизувати UI. Автотестування інтерфейсу користувача є найдорожчим у виконанні, головним чином через те, що інтерфейс змінюється найчастіше. Крім того, коли мова йде про мобільну автоматизацію, як у нашому випадку, в дію вступає багато не дуже очевидних факторів. Наприклад, особливості запуску емуляторів iOS та Android, які виявились повільними та недостатньо стабільними й надійними. Або постійні оновлення операційних систем, а разом з ними — і інструментів для автоматизації.
Наш додаток був кросплатформним Nativescript, і ми обрали Appium як основний тестовий фреймворк. Ми застосували драйвер Nativescript-dev-appium та написали Mocha-тести на Typescript. На цій же мові писався і сам застосунок.
Відповідно до концепції «Зрушення вліво», набір тестів мав бути розгорнутий у CI/CD, як і тести API. Через повільну природу емуляторів пристроїв на цей час найшвидшим варіантом для запуску UI автотестів було б використання ферми пристроїв, наприклад Saucelabs. Інакше виконання тесту займе надто багато часу, щоб вписатися в конвеєр збірки. Також інколи виконання всіх тестів по черзі займає години.
Тому ми розділили набір тестів на потоки, щоб запустити їх приблизно за 10 хвилин, використовуючи кілька хмарних пристроїв паралельно. Але на цьому етапі власники наших продуктів відмовилися від Saucelabs з міркувань безпеки: для них було неприйнятно встановлювати тестовий білд на віддалений пристрій стороннього сервісу.
Тож у нас залишився готовий до роботи набір мобільних тестів E2e, але не було можливості для їх ефективного раннього запуску.
Зміщення E2e тестів вправо
Сьогодні так багато сказано та зроблено про зміщення автоматизації вліво, але що, якщо такої можливості немає? На ранньому етапі циклу DevOps у нас був аналіз вимог, модульне тестування, а потім автоматизоване тестування API. Коли настав час провести функціональне тестування нової, щойно розробленої фічі, зробити це вручну — зовсім не найгірший варіант.
Наступний важливий етап — це регресійне тестування перед релізом, і саме тут тестування було нашим вузьким місцем. Ми повинні були охопити щонайменше 5 пристроїв Android і 5 пристроїв iOS. Виконання регресійних перевірок вручну на всіх цих девайсах/ пристроях займало у нашої команди більше як тиждень.
Як ми вже згадували раніше, кожен етап процесу DevOps вимагає різного підходу до тестування. Традиційна мобільна автоматизація e2e, яку ми мали, спочатку була призначена для запуску на ранніх стадіях. І коли ми вирішили запускати її пізніше, щоб допомогти регресійному тестуванню, ми переробили ці тести, щоб вони краще відповідали новій меті.
Що робимо далі
Перевіряємо складніші сценарії замість простіших незалежних перевірок. Задля стабільності та продуктивності тестів інженери з автоматизації зазвичай намагаються уникати тестів, що залежать один від одного. Це означає, що якщо один тест провалиться, решта не повинні провалитися через це. Недоліком цього підходу може бути менше тестове покриття. Багато важливих сценаріїв вимагатимуть численних залежних один від одного кроків.
Ми можемо знехтувати такими сценаріями, коли тести виконуються на ранніх стадіях розробки, і залишити їх для мануального тестування. Але під час регресії нам потрібно, щоб перевірки були якнайбільше наближеними до реальності, щоб справді заощадити час мануальних тестувальників.
Ретельніші перевірки через швидкість виконання. Щоб автоматизація зрушила ліворуч, важливо підтримувати швидкість виконання на належному рівні. Щоб коли розробник додає свій код в репозиторій, тести запускалися якомога швидше та давали зелене світло для включення цього коду в білд. З цієї причини більшість UI тестів не надто ретельні.
Наприклад, ми можемо перевірити наявність якогось елемента інтерфейсу на сторінці, скажімо, кнопки. Ми можемо взаємодіяти з ним, натискаючи на нього, щоб переконатися, що він працює. Але ми зазвичай не перевіряємо його положення на екрані. Або його дизайн. Або колір. Що робити, якщо кнопка того самого кольору, що й фон, невидима для людини? Що робити, якщо цієї кнопки з якоїсь причини більше немає на своєму місці, можливо, розмітка зламалася на меншому розмірі екрана? Типові автотести все це пропустять.
Оскільки ми повинні були переконатися, що додаток має хороший вигляд на багатьох різних мобільних пристроях перед релізом, це підвело нас до моменту, коли ми повинні збільшити тестове покриття автотестів. Якщо щось зміниться в інтерфейсі користувача, ми маємо побачити це на етапі регресії. Ось як ми дійшли до порівняння скріншотів.
Порівняння скріншотів. Порівняння скріншоту застосунку з еталоном забезпечує найсуворіше Pixel Perfect тестування інтерфейсу користувача. Найменша візуальна зміна стає видимою, і навіть деякі елементи, невидимі для інструментів автоматизації, таких як Appium, можуть бути протестовані таким чином. Але це уповільнює виконання тесту та додає зусиль для підтримки колекції еталонних скріншотів.
Додає складності те, що на екрані можуть бути області, що динамічно змінюються, як-от поточні дата/ час або анімація. Здебільшого це можна обійти: ми виключаємо певні області з порівняння або ж замість цілого екрану порівнюємо окремі елементи. Цей підхід не підійшов би для раннього тестування, його зірковий час настав під час регресії.
Більшість помилок, що були виявлені всією автоматизацією, було знайдено саме цим порівнянням скріншотів. Це було особливо помітно, коли, наприклад, потрібно було перевірити зміну розміру шрифту в програмі на всіх підтримуваних версіях ОС. Або швидко отримати фактичну колекцію скріншотів для всіх підтримуваних роздільних здатностей екрана для перегляду дизайну.
Параметризація набору тестів для «Лівого» та «Правого» використання. Оскільки ми додали більше рівнів перевірок для регресії, ми також зберегли опцію вимкнути ці поглиблені перевірки. Тож в перспективі можна було запускати ці ж тести раніше та швидше, лише запустивши їх з певним параметром. Для цього кожне порівняння скріншотів зберігалося в окремому тесті та поглиблені тести могли вимикатись.
Автоматизація — лише частина тестування
Не все можна автоматизувати, і це нормально. У нашому випадку push-повідомлення та біометрична авторизація завжди перевірялися вручну. А також деякі неповторювані сценарії. Для деяких таких перевірок потрібно робити те саме на великій кількості пристроїв, знову і знову. Це чудові кандидати для автоматизації.
З іншого боку, якщо мануальна перевірка займає менше часу, а автоматизація та підтримка надто складні, краще тестувати вручну. Ми підтримували документацію покриття автоматизації та тримали всю команду в курсі. Згодом автоматизовані сценарії були внесені в Azure DevOps з поміткою «Automated». Тож мануальні тестувальники знали, що покрито, а що не покрито автотестами, та могли менше займатися рутиною й бути більш творчими і дослідницькими. Скорочення часу регресії з тижнів до днів дозволило частіше робити релізи та покращило їх якість.
Нефункціональне тестування: продуктивність, безпека, сумісність. Це найважливіша річ, яку часто не помічають. Коли ми ще не отримали готовий продукт, легко припустити, що ми не можемо провести тестування продуктивності, безпеки чи сумісності на таких ранніх етапах, але це не так. Можемо, але інакше. Ми не будемо одразу тестувати його на найвищому рівні або відразу виправляти виявлені проблеми. Але було б добре вже на ранніх етапах робити принаймні базові перевірки, щоб з’ясувати, чи є проблема, яку доведеться вирішити пізніше.
Дотримуючись цієї логіки, початкове тестування продуктивності нашого проєкту полягало лише в моніторингу завантаження сервера під час виконання автоматизації API. Пізніше ми створили простий пакет навантажувальних тестів у Neoload, який наші клієнти використовували у своїй корпоративній мережі. А потім розширили ці тести і час від часу запускали їх, щоб переконатися, що ми на правильному шляху.
Або спочатку ми перевірили програму на відповідність деяким основним правилам безпеки, як-от топ-10 OWASP, і розробники також зробили деякі перевірки зі свого боку. Лише набагато пізніше ми пройшли перевірку безпеки в спеціалізованому сервісі. Вона показала, що наш продукт не має серйозних безпекових ризиків.
Ми почали перевірку сумісності, коли мануальний QA час від часу перемикався між девайсами під час функціонального тестування на ранніх етапах. Пізніше в циклі ми запускали наші e2e автотести на кожному підтримуваному пристрої. І навіть після релізу ми спостерігали за статистикою наших кінцевих користувачів. Вони можуть використовувати пристрої та версії ОС, на яких ми не тестували, і іноді стикатися з унікальними проблемами, яких ніхто не міг очікувати.
Найважливішим є результат. Оскільки ми розподіляємо тестування протягом усього циклу, як мануального, так і автоматизованого, на всіх можливих рівнях, у результаті ми маємо якісний продукт і щасливих клієнтів. Щойно ми випустили продукт — відстежуємо взаємодію та задоволеність користувачів.
Ми надсилали опитування нашим користувачам і стежили за реальними бізнес показниками, наприклад, скільки зареєструвалось нових користувачів і скільки з них продовжували використовувати наш додаток.
Це теж форма тестування. Цю інформацію використовували як зворотний зв’язок для нашого подальшого розвитку.
Дотримуйтесь найкращих практик, але не обмежуйтесь ними
Це головний урок, який ми винесли з цього проєкту. Кожен випадок індивідуальний, і кожне технічне рішення залежить від багатьох факторів. Як тестувати, коли тестувати, що автоматизувати та коли. Усе це має служити вищій меті — щасливим клієнтам, щасливим кінцевим користувачам, досягненню найвищої якості без надмірних зусиль.
Щодо автоматизації — вона може змінюватись в залежності від того, на якому етапі та для чого використовується. Іноді навіть всупереч загальноприйнятим практикам.
4 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарівдякую за статтю!
кілька моментів хотів би сказати:
схоже, що це TDD піхід, який сам по собі вже є tricky, бо я чув успішні кейси його застосування хіба що в казках. не в останню чергу через те, що вимоги з часом змінюються (і це нормально), а для підходу test-first адаптовуватись до них не так легко і швидко.
це звучить дуже дивно.
як вони собі уявляли швидкодію і надійність тоді?
я бачу тут одну з двох потенційних проблем — або менеджери команди з самого початку не донесли до замовників, що емулятори то дуже лагаюча штука і треба юзати щось незалежне, або власники міняють свою думку в останній момент і тоді все накривається.
і оцей момент з порівнянням скрінів — я не дуже зрозумів, але це типу як заміна юай автотестів?
бо нема можливості проганяти їх?
якщо так, тоді це напівміра
Привіт, дякую за статтю. То скільки у вас зараз займає регресія?
І якою лібою порівнюєте скріншоти? Порівнюються просто картинки чи скріншоти DOM нод?
Дякую за статтю <3
помилка в слові релізити