Організація процесу CI для швидкої доставки збірок. Контроль якості на його етапах
Доброго дня! У даній статті мені би хотілося познайомити читачів із тим, як ми у Wire організували процес безперервної інтеграції у нашому проекті, чи, правильніше було би сказати проектах, оскільки ми підтримуємо кілька основних платформ. Наш основний і поки що єдиний продукт — це одноіменний онлайн-месенджер, а метою є створення платформи з відкритим вихідним кодом, яка буде одночасно зручна для користування, надійна та, що особливо актуально в світлі останніх новин про успіхи Великого Брата, повністю захищена від прослуховування через використання найбільш просунутих на даний час криптографічних алгоритмів.
Особливо хотілося би приділити увагу мобільним платформам, оскільки інформації в мережі обмаль, і для задач, які виникали перед нами під час налагодження процесу, доводилось винаходити оригінальні рішення.
Основними вимогами до процесу CI в нашому оточенні є можливість швидкої доставки (quick delivery) збірки, яка буде при цьому достатньо стабільною, щоб в стислі терміни підготувати її до релізу, не жертвуючи при цьому якістю. Тривалість спринтів для кожної команди різна, але в загальному вона не перевищує двох тижнів, тому дуже гостро постає проблема збереження якості продукту при постійно зростаючій складності та обмеженості в ресурсах.
CI — Unit- та Integration-тестування
Перший рівень CI — Unit- та Integration-тестування. Ось так виглядає взаємодія усіх компонентів в системі:
Continuous Integration охоплює усі компоненти як окремо, так і в інтеграції з усіма іншими. Це, перш за все, Unit- та Integration-тести, які підтримуються безпосередньо розробниками самих компонентів та запускаються автоматично при кожному коміті. Тестування ніколи не буває забагато:).
Щодо технологій, які використовуються на даному етапі, то тут також все дуже залежить від компонента та від команди. Наприклад, Sync Engine для Android написаний на Scala і, відповідно, менеджмент юніт-тестів відбувається через стандартний для таких цілей ScalaTest фреймворк. Ці тести автоматично запускаються Jenkins’ом після кожного мерджу в основну гілку. Далі той же Jenkins збирає показники покриття тестами коду (Code Coverage) та публікує їх у вигляді графіку на внутрішньому Dashboard’і:
В інших компонентах для інтеграції також, здебільшого, використовується Jenkins, але це не обов’язково. Наприклад, збірка WebApp відбувається в Travis.
Структура репозиторію
Правильно організована структура репозиторію — важливий фактор для стабільності збірок.
Усі компоненти мають дві або три гілки, в яких проходить основна розробка. В кожній команді ці гілки називаються інакше, але в загальному їх можна розділити на:
Development — це гілка текучої версії, яка зараз знаходиться в активній розробці. Туди мерджаться усі pull request’и, які пройшли верифікацію, і в ній відбувається внутрішнє тестування. Це найбільш нестабільна гілка, і нею користуються тільки розробники та QA.
Internal — відгалуження Development. Це збірка, яка визнана стабільною настільки, що нею можна користуватись в межах офісу. Тобто це для внутрішнього використання та тестування, — dog-food.
Candidate — це збірка, яка містить усі фічі, які були заплановані для даної ітерації, і яка пройшла етап Internal-тестування. Для цієї збірки виконується передрелізне тестування і, якщо вона задовольняє усім вимогам, то може стати Release Candidate’ом і далі бути випущеною в світ.
Дана структура візуально приблизно виглядає таким чином (деталі можуть відрізнятися в залежності від конкретного компонента):
Виявлення дефектів
Добре організованийCI-процес повинен виявляти потенційні дефекти на якомога ранніх стадіях розробки.
Тут мені хотілося б трохи детальніше зупинитись на end-to-end тестуванні, оскільки це та частина системи CI, де внесок QA найбільший, і де, потенційно, виникає найбільше проблем, оскільки перевіряється взаємодія усіх компонентів системи. На цьому етапі у нас є, фактично, сім продуктів, кожен з яких має унікальні сценарії.
Відразу зауважу, що згадування якоїсь конкретної технології в цій статті не є рекламою тої компанії, яка за нею стоїть. Просто так склалось, що ми використовуємо саме її, а не щось інше — в силу тих чи інших особливостей цієї технології чи програмного рішення, які стали вирішальними саме для наших продуктів.
Отже, розробка фічі розпочинається зі специфікації, яка готується дизайнерами. Потім дана специфікація обговорюється з QA та Product Owner’ом. У неї, якщо необхідно, вносяться правки, і далі вона передається розробникам. Поки розробники кодять фічу, можна вже продумати та описати тестові сценарії для неї. Для того, щоб зберегти та структурувати ці кейси, ми використовуємо Test Case Management-систему Testrail. Після того, як фіча готова з точки зору розробників, і юніт тести для неї написані, на дану фічу робиться Pull Request. Далі на цей Pull Request повинні дати добро інші розробники (Code Review), дизайнери і, нарешті, QA. Ця процедура також різниться для різних команд, наприклад, для Android-клієнта Code Review, Design Review та QA Review робиться паралельно, а для iOS — послідовно. Для Android Pull Request’и створюються в GitHub і мають налаштований тригер, який автоматично ініціює Jenkins збірку для конкретної гілки і атачить її до даного Pull Request’а, як тільки в ньому є нові коміти:
Також налаштована інтеграція між Jira та GitHub, що дозволяє бачити усі коміти у відповідних Jira-тасках, які з ним пов’язані. Таким чином, в Android робота зі Sprint Board йде більш опосередковано — як наслідок обробки Pull Requesti’ів. Інакше це організовано в iOS — там основна робота зосереджена в Jira Sprint Board, а збірку можна отримати з гілки Development, як тільки відповідний Pull Request пройшов Code Review:
Тому iOS-Jenkins працює по трохи іншому принципу, ніж Android-Jenkins, і перш за все заливає відповідні білди для трьох основних гілок в Hockey App. Для Android теж є така можливість, але оскільки активні Pull Request’и ще не доступні в гілці Development, то їх, в основному, встановлюють «вручну», використовуючи adb. Також Hockey App використовується для заливки різних гілок standalone-додатку для Windows та Mac, які фактично являють собою native wrappers над WebApp (детальніше можно почитати на electron.atom.io). З HockeyApp «забирають» свіжі збірки Internal-користувачі, оскільки в HockeySDK є можливість автоматично оновлювати аплікації для мобільних платформ, що є дуже зручно для наших колег. А для нас, в свою чергу, зручно збирати та категоризувати crash-звіти та feedback’и.
Разом з тим, усі збірки також заливаються до спеціальної корзини (bucket) Amazon S3, де зберігаються назавжди. Кожна така збірка містить крім, власне, версії, унікальний для своєї гілки Build Number, по якому її можна потім відшукати. Звідси вони вибираються для автоматизованого end-to-end тестування QA Jenkins-сервером.
Автоматизація
Неефективна автоматизація завжди буде потребувати більшого вкладання ресурсів та капіталу, ніж взагалі її відсутність.
Тут ми, якраз, повернулись до тієї проблеми, яка піднімалась на початку статті, про нестачу ресурсів і шляхи її вирішення. Зараз, після більше ніж одного року експериментів та вдалих і невдалих спроб, ми маємо стабільний процес, який тісно вплетений в нашу систему безперервної інтеграції, і в якому близько 80% тестів для мобільних платформ автоматизовано, і ці тести дійсно знаходять баги і спрощують нам життя.
Такий стан справ став можливий завдяки двом факторам:
— постійному моніторингу тестів та підтримці їх в актуальному стані;
— такій конфігурації процесу, яка дозволяє максимально спростити та приховати внутрішню складність системи автоматизації.
З першим пунктом все зрозуміло, а от другий потребує постійного фідбеку від тих людей, які беруть участь в процесі. Все, що може бути спрощене, повинно бути спрощене.
Наша система автоматизації включає в себе єдиний Java-фреймворк, який містить в собі підпроекти для усіх підтримуваних платформ. Основа універсальності даного фреймворку — використання високорівневого Selenium API, яке на нижчих рівнях абстракції відповідними інструментами трансформується у виклики, типові для конкретної платформи. Так, для мобільних платформ таким проксі служить Appium, а для Desktop — Appium For Mac. На найвищому рівні абстракції знаходяться Gherkin-сценарії, які інтерпритує Cucumber JVM.
Автоматизовані end-to-end тести для мобільних платформ запускаються автоматично кожної ночі для найсвіжішої на даний час збірки з гілки Development. Ці тести включають в себе наступні основні множини:
— VerificationRun — це спеціальна динамічна множина тестів, в яку додаються усі тести, які були неуспішними після останнього запуску, крім тестів, де неуспішною була фаза налаштування. Ми спеціально виділили такі тести в окрему множину, оскільки буває таке, що в продукті виявляється мінорний баг, що призводить до падіння одного або більше тестів, і цей баг не фікситься відразу. Тому такі тести автоматично позначаються спеціальним маркером в TestRail (усі автоматизовані тести мають унікальний ідентифікатор, який співпадає з їх Testrail ID), і потім Jenkins автоматично синхронізує даний маркер (синхронізація двостороння). Таким чином ми знаємо, що тести, які знаходяться в даній множині, здебільшого, попали сюди надовго і, крім того, вони не створюють зайвого «шуму» при виконанні регресійних тестів. Тест автоматично виключається з верифікації і потрапляє назад до своєї «рідної» множини, як тільки він перестає «падати».
— RegressionRun — в дану множину включаються усі тести, які тестують регресію. Failed тести в регресії — це перший привід приділити увагу даному сценарію в новому білді.
— CallingBasicRun, CallingBasicRun — спеціальні набори тестів, які перевіряють різні сценарії здійснення відео- та аудіодзвінків. Для цих потреб ми також створили спеціальний сервер, який має REST API-інтерфейс, і може здійснювати, приймати та контролювати стан дзвінків.
— StagingRun — це нові тести, які тільки пройшли внутрішній Code Review і тепер проходять «обкатку» в оточенні Jenkins. Зазвичай, тести затримуються тут тільки на кілька днів.
Кожен запуск тестів генерує JUnit-сумісний звіт, який потім трансформується Jenkins-pugin’ом в HTML-звіт зі скріншотами після кожного кроку. Посилання на цей звіт розсилається усім учасникам, що задіяні в тестуванні даної платформи. Звіт аналізується QA кожного ранку. Звичайно, якщо в білді не відбулося серйозних змін інтерфейсу, такий аналіз займає десь біля години для всіх репортів:
В результаті аналізу звіту приймаються рішення про відкриття нових багів або фікси тестів, якщо проблема не в продукті. Automation Jenkins також уміє:
— запускати «мобільні» тести в паралелі на різних нодах і для різних версій конкретної платформи;
— верифікувати відповідні ноди на предмет можливості запуску на них тестів;
— динамічно включати та виключати ноди із множини та повідомляти про це на email;
— синхронізувати стан тестів з Testrail;
— публікувати агреговані результати тестів на Dashboard (у нашому випадку це www.geckoboard.com);
— проводити Upgrade- та Performance-тести;
— та багато інших цікавих речей, кожна з яких може бути темою для окремого циклу статей, якщо шановній публіці буде цікаво.
Наше тестове середовище включає кілька десятків віртуальних машин, які фізично розташовані в офісі. Для тестування Android використовуються «справжні» пристрої — телефони та таблетки популярних моделей і версій мобільної OS. В iOS для автоматизації, здебільшого, використовується симулятор, що пов’язано з великою кількістю обмежень та багів (як в Appium, так і Apple Instruments) при використанні «справжнього» пристрою. Але є також деякі сценарії, що обов’язково потребують «справжніх» телефонів, наприклад ті, які використовують камеру.
Мануальне тестування передрелізних збірок
Мануальне тестування буде необхідне завжди; питання тільки — в якому об’ємі.
Для Candidate-збірок є спеціальна процедура перевірки, в процесі якої в Testrail створюється Test Plan із набором базових регресійних тестів, які збірка повинна пройти. Даний набір, в залежності від платформи, включає близько сотні тестів, і ця кількість постійно зростає. Навіть просто вручну заповнити результат для кожного тесту, дивлячись на репорт, було би досить складно та затратно в часі, тому для таких цілей ми використовуємо інтеграцію з Testrail API, що дозволяє просто передати Jenkins’у параметри відповідного с’юта, і він автоматично заповнить усі результати, і, більше того, додасть коментар з адресою відповідного job’а у всі оновлені тести:
Нам залишиться тільки проставити результати для тих тестів, які не містять маркера Is Automated (що теж синхронізується автоматично, як тільки тест додається у відповідний набір). На даний час кількість таких тестів, які потрібно виконувати вручну, не перевищує
Локалізація
Навіть локалізація є частиною Continuous Integration. Так, при кожній збірці гілки Development Jenkins автоматично витягує необхідні ресурси та за допомогою REST API заливає їх в Crowdin. Далі при збірці Candidate, використовуючи той же API, можна отримати статус перекладу для локалі та «завалити» збірку, якщо проект все ще потребує перекладів. В іншому випадку автоматично завантажити перекладені ресурси та включити їх в компіляцію.
На цьому поки що все. Дякую за увагу та бажаю успіхів усім колегам в намаганнях зробити цей світ кращим!