Організація процесу 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 (що теж синхронізується автоматично, як тільки тест додається у відповідний набір). На даний час кількість таких тестів, які потрібно виконувати вручну, не перевищує 15-20%. Всі інші кейси автоматизовані та заповнюються Jenkins’ом під час виконання множини передрелізних тестів.

Локалізація

Навіть локалізація є частиною Continuous Integration. Так, при кожній збірці гілки Development Jenkins автоматично витягує необхідні ресурси та за допомогою REST API заливає їх в Crowdin. Далі при збірці Candidate, використовуючи той же API, можна отримати статус перекладу для локалі та «завалити» збірку, якщо проект все ще потребує перекладів. В іншому випадку автоматично завантажити перекладені ресурси та включити їх в компіляцію.

На цьому поки що все. Дякую за увагу та бажаю успіхів усім колегам в намаганнях зробити цей світ кращим!

Все про українське ІТ в телеграмі — підписуйтеся на канал DOU

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn

Схожі статті




39 коментарів

Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.

Круто, що з’являються статті українською мовою — це дійсно важливо. Дякую за статтю, дуже цікаво було прочитати. Чекаю на продовження!

Ви теж приєднуйтесь — або розкажіть колегам чи друзям, яким було б цікаво написати статтю:
dou.ua/add-post

Спасибо за статью. У меня есть вопрос по JIRA board: почему у вас все промежуточные состояния типа «In Progress»? Нет состояний Development Done, Review Done, QA Done. Как, например, лид понимает, что задача выполнена девелопером и необходимо делать review? Только по приходящему пулл реквесту?

Кожна команда відповідальна за задачки в своїй колонці. Так наприклад, девелопер бере собі задачу з TODO і переміщає в Development (статус In Progress). Коли він робить Pull Request, то, відповідно, сам оновлює статус тікета, і те ж саме, коли PR змерджений. Далі, коли тікет потрапляє в Design Review, то за його оновлення стає відповідальним дизайнер, який робить перегляд. Далі QA. При цьому поле Assigned To не змінються, тому якщо тікет повертають назад на доробку, то девелопер знає, що це його задача. PM тільки слідкує за тим, щоб тікети надто довго не затримувались в проміжних станах.

А если кто-то забудет сменить статус айтема в JIRA — как скоро отслеживаете?

Є чітко визначена тривалість спрінта. Як я написав вище, PM слідкує за дашбордом та «пінгує» команду, якщо тасків в черзі багато, і є ризик, що вони не будуть готові вчасно.

чи плануєте використовувати workflow triggers:
confluence.atlassian.com/...kflow-automation-triggers
це частково вирішує проблему «забув оновити статус»

Насправді, цей плагін уже працює для iOS — наприклад, для жира-тасків автоматом оновлюється статус на основі стану відповідного Pull Request’a в git. Але такий плагін не сильно допоможе у випадку з колонками Design Review та QA Review, де все-одно потрібна взаємодія з людиною.
З Андроїдом ще складніше — там Pull Request не закривається, поки не пройде всі стадії перегляду в Git. І якщо вже брати якийсь базис для трігерів, то це будуть кастомні лейби зі своєю особливою логікою.
Плюс є типи задач, для яких Pull-Request’ів немає в принципі — наприклад, по налаштуванню оточення, дослідженню, створенню прототипів і тд.

Ще така штука — в Jira налаштована інтеграція з GitHub, тому в кожному тікеті видно всі гілки та Pull Request’и Git, в яких був згаданий ID цього тікета:
marketplace.atlassian.com/...connector-plugin/versions
marketplace.atlassian.com/...it_plugin/server/overview

Промежуточные состояния могут понадобиться если разработчики и тестировщики работают в отдельных командах. В кросс-функциональной команде с короткими итерациями все проще: есть спринт, короткий понятный набор историй, команда работает на доведение их до конца. Если что-то «застряло», об этом станет известно на следующем дейли стендапе.

Дякую за статтю.

Прекрасна стаття, дякую!

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

Я постарався виділити спільні характеристики, типові для більшості проектів. Плюс, не хотілося сильно перевантажувати статтю.
Для прикладу, в iOS як менеджер залежностей використовується Carthage github.com/Carthage/Carthage Ми для нього просто пишемо конфіг, які версії модулів брати і звідки, а далі уже справа техніки. В інших проектах організація своя, бо стек технологій сильно різниться.

як часто проходить update залежностей? Виконує це jenkins, чи руцями міняють версії?

Так, це виконує Jenkins перед збіркою Master-проекту, який потребує цих модулів. Самі ж версії вказуються вручну, в залежності від того, що потрібно мати. Для деяких модулів вказується конкретний номер версії, для інших — тільки мінімальний (в такому випадку менеджер сам повинен витягнути актуальний номер версії модуля і стягнути її).

Тішуся, що на ДОУ все більше й більше контенту українською мовою :) Дякую автору за чудову статтю!

Якщо напишете ще одну статтю, то буде ще більше контенту українською мовою:
dou.ua/add-post

обов’язково :) от тільки розгребуся трохи з поточними справами — і шось напишу

Спасибо за статью. Есть несколько вопросов: как определяете парк андройд девайсов ? Физически-подключенные девайсы к виртуалкам всегда там и остаются ? Сколько по времени прогоняются тесты на аппиуме ?

Щодо парку девайсів є кілька джерел:
— Огляди популярності Андроїд-девайсів в мережі
— Аналіз анонімної статистики отриманої від кастомерів
— Аналіз фідбеків від сапорта

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

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

Да, было бы интересно почитать про процесс, который описан более детальней.

тести автоматично запускаються Jenkins’ом після кожного мерджу в основну гілку.

Тобто якщо змерджили какульки, то будемо знати про це після мерджу? а чому не змерджити $targetBranch в гілку спочатку, протестувати, а потім вже думати чи мерджити $sourceBranch в $targetBranch? В дженкінсі це робиться доволі просто гіт плагіном.

«Какулька» це дуже відносне поняття. Але завжди проблему краще попередити, ніж боротися з нею. Саме тому розробники та дизайнери є такими самими учасниками контролю якості, як і QA. Це значить, що коли робиться Code Review, наприклад, то також виконується мінімальне позитивне тестування, що дозволяє позбутись очевидних недоліків. Всі ці речі, які описані в статті поодинці не дають такого ефекту, як в комплексі.
Дуже важливо для всіх учасників проекту розвивати колективну відповідальність за результат роботи і бачити «ліс за деревами».

Не бачу нічого відносного в слові «какулька».

Це значить, що коли робиться Code Review, наприклад, то також виконується мінімальне позитивне тестування, що дозволяє позбутись очевидних недоліків

Ревьювер дебажит код чужого разработчика во время ревью да еще и косяки находит так? Похоже пока трудно у вас с коллективной ответственностью раз разработчики не делают то, что в они впервую очередь должны сами сделать — выполнить свой код и убедиться что он работает.

Есть такая штука командная работа. Не слыхал?

Це нормально — я ж не казав, що робиться повне тестування «від і до». Але якщо інший розробник бачить, що там явний косяк, то він не буде мовчати. Крім того, це чисто психологічна штука, — якщо я сам написав код, то мені об’єктивно складно буде знайти в ньому проблеми. У людей позитивне мислення щодо того, що вони роблять, і з цим треба миритись.

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

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

Код ревью это прекрасная практика. Но как показывает опыт, не всегда он оберегает от «Какулек». А если в процесс мержа в Deployment ветку, добавить например Sonar?

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

Спасибо за статью, очень информативная!

А почему Appium, а не Calabash? Что то все на него подсели так жестко, хотя там хватает проблем, разумеется как и в калабаше.

Багато причин: наприклад, знавців Java на порядок більше, ніж любителів Ruby. Велика кількість допоміжних бібліотек (той же OpenCV). Ну і «статичність», звичайно. Не знаю як кому, але мені набагато простіше робити рефакторінг великих Java проектів, ніж той самий Python чи Ruby. В той же час, у фреймворку є багато допоміжних утиліт на Пайтоні, — просто для кожної гайки підбираємо свій ключ :)

Спасибо за конструктивный ответ, а не воду, как обычно люди используют. Желаю Вам только успехов, а я буду вариться дальше в своем калабашном мире с Ruby :P

Є ще calabash-jvm, але Appium, об’єктивно, має кращу підтримку від спільноти, і баги там вирішують більш оперативно, як і впроваджують нові фічі (уже є досвід звітування проблем в оригінальний проект).

Как в прошлом вовлеченный в сей процесс человек — appium был выбран еще и потому, что нужно было писать тесты для OSX приложения в том числе, на тот момент — только appium предлагал в рамках «одного» фреймворка писать тесты и для iOS и для Android и для OSX. Ну и веб приложение было на горизонте (тогда еще) и удобно было бы использовать похожую технологию для него. Как то так.

Интересная статья, спасибо!

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