Як налаштувати автоматизацію тестів локалізацій на прикладі CleanMyPhone
Всім привіт! Мене звати Наталя, і я — уже 6 років QA Engineer в MacPaw на iOS-проєкті, який за цей час пережив кілька масштабних редизайнів. Це завжди вимагало ретельного тестування локалізацій, що часто займає багато часу. У цій статті я поділюся своїм досвідом налаштування автоматизації цього процесу.
Інформація буде корисною для тих, хто хоче налаштувати автоматизоване тестування локалізацій у своєму проєкті, зекономивши час і ресурси.
Про проєкт
Наш проєкт — це мобільний застосунок CleanMyPhone для iOS та iPadOS. Під час його редизайну, було прийнято рішення автоматизувати процес тестування локалізацій. Адже у нас є підтримка 11 локалізацій, горизонтальної та вертикальної орієнтації екранів, 23 пристрої на iOS-платформі та 23 пристрої на ipadOS-платформі.
Найменша зміна тексту у назві кнопки вимагає перевірки цієї кнопки одинадцятьма різними мовами у двох орієнтаціях екранів на девайсах з різними діагоналями, що породжує десятки повторних кроків задля однієї маленької зміни. Наприклад, у кнопки був лише один стан «Scan», але тепер їй додали ще один — «Restore». А це означає, що потрібно перевірити її на інших мовах, особливо на тих, які мають довгі слова. Якщо зміни більш глобальні, скажімо, редизайн проєкту, то це зовсім інший рівень зусиль.
Задача постала наступна: автоматизувати тестування локалізацій шляхом написання UI-тестів, щоб за потреби проганяти їх на CI/CD на потрібних локалізаціях, орієнтаціях екрана та моделях iPhone/iPad. Пройдемось покроково від початку автоматизації до оптимізації її швидкості.
Набір інструментів:
- Для написання автотестів — XCUITests.
- Для знімків екранів — fastlane snapshot.
- Для автоматизації прогонки автотестів — Azure DevOps CI/CD.
- Для звіту з результатами — html звіт від fastlane.
Планування
Спочатку необхідно спланувати, які екрани застосунку та UI-елементи потрібно перевірити. На цьому плані базуватимуться автотести, в ході яких робитимуться скриншоти та зберігатимуться для HTML-звіту.
Декілька порад, як краще планувати тести:
- Зосередитись на місцях, які найбільш ймовірно будуть часто змінюватись, а отже потребуватимуть частої перевірки локалізацій.
- Не завʼязуватись на функціонал чи UI-елемент, а краще навігуватись між екранами застосунку, коли відбуваються ті чи інші зміни в UI. Оскільки для перевірки локалізацій нас цікавитиме саме наповнення екранів, а не точність відпрацювання функціоналу.
Наприклад, у нашому застосунку місце, на якому текст часто змінюється — це онбординг на першому запуску. Якщо з’явиться новий функціонал в застосунку, буде змінюватись онбординг, в якому додаватиметься новий текст опис цього функціоналу. Відповідно тест полягав в тому, щоб під час навігації між екранами онбордингу зробити знімок того чи іншого екрану в горизонтальній та вертикальній орієнтаціях.
Написання UI-автотестів
Для автоматизації тестування на iOS та iPadOS використовуємо XCUITests-фреймворк.
Раджу всі тести, які стосуватимуться локалізацій, додавати в окремо створений Xcode Test Plan. Це важливий крок, оскільки він полегшує інтеграцію з CI/CD, що для нашої задачі є суттєвою перевагою.
Увага! Якщо на проєкті ще не використовуються UI-тести, то необхідно спершу створити новий тестовий таргет. Як це зробити, можна переглянути тут, де все коротко і доступно показано.
Створити тест-план досить легко. За умови, якщо уже доданий таргет UI Testing Bundle, потрібно:
- Вибрати схему проєкту, натиснути редагувати її, та у вікні, що відкриється, в категорії Tests вибрати опцію «Convert to Use Test Plan».
- Далі відкриється ще одне вікно, де слід вибрати опцію «Create Empty Test Plan».
- Після чого натиснути Convert, і нарешті на останньому вікні вказати, де розмістити свій тест-план та назвати його.
- І останнє. В Test Navigator вибираємо щойно створений тест-план і через + кнопку додаємо бандл з UI-тестами. Ті тести, які будуть використовуватись в тестуванні локалізацій, залишаємо enabled, а всі інші робимо disabled.
Отже, коли всі налаштування готові, залишається доповнити тест-план запланованими тестами, і переконатись, що вони проходять без помилок.
Використання Fastlane Snapshot
Fastlane — доволі зручний інструмент для розгортання та випуску мобільних застосунків. Він дозволяє автоматизувати рутинні задачі (наприклад, збірка різних версій застосунку) як для середовища розробки, так і для їх публікацій в App Store. Серед інших задач, які нам цікаві — це збирання скриншотів, що ми й використаємо для автоматизації тестування локалізацій. Як це працюватиме: в процесі UI-тестів будемо проходитись екранами застосунку, і в потрібний момент викликати метод fastlane, який буде робити скриншот.
Налаштовуємо fastlane для проєкту
Інструкцію з налаштування можна знайти тут: Fastlane screenshots for iOS: Setting Up snapshot:
- Встановлюємо fastlane, знаходячись в директорії проєкту
fastlane snapshot init. - Після успішного встановлення помічаємо в директорії проєкту новий файл — SnapshotHelper.swift, який переносимо до UITest-групи в проєкті.
- Далі відкриваємо проєкт, вибираємо редагувати схему, та в секції build ставимо галочку в колонці run тест-плану з локалізаціями. Також ставимо галочку в Shared.
Оновлюємо класи з тестами
- В setup() метод додаємо fastlane snapshot метод — setupSnapshot(XCUIApplication) — принаймні, так каже робити документація.
Однак тут варто зауважити, що в одному з останніх апдейтів fastlane стало обовʼязковим використання @MainActor в методах, де викликаються методи fastlane snapshot.
Ця зміна була внесена для покращення сумісності з асинхронним виконанням коду при роботі з UI. Оскільки використання setupSnapshot() може передбачати асинхронні операції, такі як завантаження даних (яке повинне виконуватися на головному потоці, щоб уникнути блокування UI), використання @MainActor гарантує, що всі асинхронні дії будуть виконуватись у правильному контексті.
Отже, додаємо @MainActorдо setup() метода в класі з тестами, а в самому setup() методі викликаємо fastlane snapshot метод — setupSnapshot(XCUIApplication).
class LocalizationsOnboardingUITets: { private let app = XCUIApplication() @MainActor override func setUpWithError() throws { setupSnapshot(app) app.launch() } }
- У кожній тест-функції, де ми будемо робити скриншот, теж додаємо @MainActor. В саму функцію додаємо snapshot("Назва екрана або елемента") метод, який і буде робити скриншот застосунку.
Налаштовуємо локалізації та пристрої, на яких будуть проходити тести
- Після інсталяції fastlane snapshot в корені проєкту, окрім SnapshotHelper.swift, також можемо побачити новий Snapfile.
- Відкриваємо файл та обираємо мови, якими будуть проходити тести. Наприклад, у нашому проєкті їх 11 без англійської мови.
- Обираємо пристрої, на яких будуть проходити тести.
Моя порада в такому випадку шукати золоту середину між діапазоном діагоналей пристроїв, які підтримує ваш застосунок, та аналітики стосовно того, які пристрої найчастіше використовує ваша активна аудиторія. Ми запускаємо перевірку локалізацій щонайменше на трьох пристроях: iPhone SE (3rd generation), iPhone 14 Pro Max та iPad mini (6th generation). Звісно, цей список із часом змінюється.
- Далі ми виконуємо команду fastlane snapshot, перебуваючи в корені проєкту. Якщо хочете спробувати, як працює fastlane snapshot на одному тесті, використайте параметр only_testing і шлях до тесту:
fastlane snapshot --only_testing "MyProjectUITests/LocalizationsOnboardingDemo/testInitialOnboardingScreen"
Взагалі, запамʼятайте цей параметр, він ще знадобиться. - По завершенню тестів в браузері автоматично відкривається HTML-посилання із fastlane-звітом — reports_generator.rb, де будуть зібрані усі скриншоти.
Інтеграція з СI/CD
Можна було б зупинитись на тому, щоб запускати тести локально за потреби перевірити локалізації. Але якщо перемістити цей процес на віддалену машину, звільняється багато часу та простору на інші задачі.
У нашому проєкті всі тести автоматично запускаються в процесі CI/CD на платформі Azur DevOps в окремих пайплайнах, тому саме на ньому базуватиметься мій приклад.
Щоб налаштувати пайплайн, на якому б проходили наші тести, знадобляться:
- .yaml — конфігурація, де вказується послідовність завдань, необхідних для автоматизації на Azure CI/CD;
- Fastfile — файл, який розташовується в директорії fastlane/ проєкту, що створюється після ініціалізації Fastlane. Якщо ви ще не використовуєте Fastlane, тут можете знайти інструкцію з його налаштування.
- У Fastfile ми пропишемо команду, яка запускатиме задачу з тестування локалізацій, яка буде потім передаватись в .yaml, щоб запустити її на Azure.
- Уже відомий нам Snapfile.
Налаштовуємо Snapfile
У цьому файлі, окрім локалізацій та пристроїв, слід вказати:
- app_identifier("") - bundle Identifier для вашого застосунку.
- scheme("") - схема вашого застосунку, яка збирається, коли запускаються тести.
- testplan("") - тест-план, в який ви додаєте тести, що стосуються перевірки локалізацій.
- output_directory("./fastlane/screenshots") — важливо вказати, щоб, коли тести проходили на CI, було відомо місце, де повинні зберігатись скриншоти.
- clear_previous_screenshots(true) — важливо вказати, щоб не накопичувати попередньо зроблені скриншоти.
- reinstall_app(false) — опційно, але я раджу використовувати. Оскільки це означатиме, що застосунок не буде видалений і повторно встановлений при кожному запуску нового тесту, що може пришвидшити час виконання тестів.
Розробляємо ‘lane’ на створення скриншотів під час прогонки тестів у Fastfile
lane — це блок коду в Fastfile, який визначає послідовність дій, що мають бути виконані для конкретної задачі. Кожен lane може містити різні команди (actions), що можуть бути використані для збірки, тестування чи деплою. У нашому випадку для запуску fastlane snapshot використаємо команду ‘snapshot’ та додамо декілька параметрів, які, звичайно, можуть мінятись залежно від потреб:
lane :screenshots do snapshot( concurrent_simulators: true, skip_open_summary: true, derived_data_path: Actions.lane_context[SharedValues::SCAN_DERIVED_DATA_PATH] ) end
Де:
- concurrent_simulators: true — зробить скриншоти на кількох симуляторах одночасно;
- skip_open_summary: true — по завершенню тестів HTML-репорт не відкриватиметься автоматично;
- derived_data_path вказує на директорію, де Xcode зберігає результати збірки, результати тестів, лог-файли;
- [SharedValues::SCAN_DERIVED_DATA_PATH] — константа, яка представляє ключ для отримання шляху до DerivedData, що був встановлений або використаний під час запуску тестів. Я її використала з документації.
- Actions.lane_context — об’єкт, який Fastlane використовує для зберігання та передачі даних між різними діями (actions) під час виконання lane. Про lane_context більше тут.
Створюємо пайплайн в Azure DevOps та прописуємо до нього інструкції в YAML-конфігурації, де буде викликатись команда (lane) з Fastfile
Оскільки в кожного проєкту свої особливості налаштування пайплайнів, я зупинюсь на тій частині, якa стосується етапу запуску тестів.- Створюємо задачу ʼjobʼ за допомогою YAML-конфігурації, в якій будемо викликати команду, яку ми щойно прописали в Fastfile.
jobs: – job: screenshots displayName: 📸 Localizations UI Test
- У ній створюємо крок, де виконуємо команду для знімання скриншотів за допомогою Fastlane, інструкцію до якої ми прописали в lane :screenshots в Fastfile.
steps: – script: | bundle exec fastlane screenshots name: RunningTests displayName: 🤖 Run Screenshots Testing
- Після завершення цього кроку нам потрібно зайти в директорію, де лежать скриншоти, додати їх в архів і перемістити до директорії, де зберігаються артефакти збірки. Цей крок потрібен для того, щоб потім побачити цей архів в інтерфейсі Azure DevOps та завантажити його собі для перевірки результатів.
Ми памʼятаємо, що директорію, де лежать скриншоти, ми вказали в Snapfile в параметрі output_directory("./fastlane/screenshots«). А шлях до директорії, де зберігаються артефакти збірки на Azure DevOps, представлений змінною: $(Build.ArtifactStagingDirectory). Більше про це можна дізнатися тут: Azure DevOps Build Variables.
Отже, ми маємо всі дані для наступного кроку:
– bash: | cd $(Build.SourcesDirectory)/fastlane zip -r screenshots.zip screenshots mv screenshots.zip $(Build.ArtifactStagingDirectory) condition: succeeded() displayName: 📊 Report Publish
Ось ми й поєднали UI tests з Fastlane Snapshots та Azure DevOps. Додавши інші параметри в YAML-конфігурацію, які використовуються на вашому проєкті, ви можете створювати новий пайплайн та пробувати запускати тести.
Оптимізація
Створення можливості запускати окремі тести з тест-плану на віддаленій машині
Задача, описана вище, в .yaml-конфігурації запускає всі тести, що написані й додані в тест-план. Це позбавляє нас гнучкості, коли потрібно запустити один тестовий метод, який зазнав змін і потребує перевірки.
Запускаючи fastlane snapshot, ми використовували параметр only_testing, де вказували, який саме тест хочемо запустити. Тепер потрібно створити зміну, в яку буде передаватись назва тесту, яку ми будемо вказувати в інтерфейсі Azure DevOps перед тим, як запустити пайплайн. І яка потім буде присвоюватись параметру only_testing.
Для цього ми використаємо ‘env’-змінні, тобто environment variables. У контексті YAML-пайплайнів для Azure DevOps, environment variables — це змінні, які будуть доступні для конкретного кроку (step) у пайплайні. Більше про environment variables можна дізнатись тут.
Тобто нам потрібно створити цю зміну в кроці, де запускаються тести:
steps: – script: | bundle exec fastlane screenshots name: RunningTests displayName: 🤖 Run Screenshots Testing env: TEST_TO_RUN: $(RUN_TO_TEST)
Після цього кроку відкриваємо наш Fastfile, та в lane :screenshots оголошуємо нову змінну і її значення передаємо в параметр only_testing:
lane :screenshots do onlyTestingValue = ENV['TEST_TO_RUN'] snapshot( concurrent_simulators: true, skip_open_summary: true, derived_data_path: Actions.lane_context[SharedValues::SCAN_DERIVED_DATA_PATH] only_testing: onlyTestingValue ) end
І нарешті, коли ми захочемо запустити тести на СI, потрібно зайти на пайплайн, натиснути ‘edit’ -> ‘variables’ -> ‘+’ -> і додати нашу змінну, куди передати тестовий метод.
Пришвидшення часу проходження тестів
Перші запуски всіх локалізаційних тестів в проєкті у нас займали ~6 годин. Результат, мʼяко кажучи, незадовільний. Однак, це не дивно. Через те, що у нас 11 локалізацій, і 3 пристрої, застосунок білдився 33 рази для виконання одного і того ж тесту. Отже, постала задача зробити так, щоб застосунок білдився один раз для виконання одного і того ж тесту 33 рази.
Для цього в lane :screenshots ми використали ще одну команду: run_tests, з необхідними параметрами:
lane :screenshots do path = “Actions.lane_context[SharedValues::SCAN_DERIVED_DATA_PATH]”, onlyTestingValue = ENV['TEST_TO_RUN'] run_tests( reset_simulator: true, build_for_testing: true, derived_data_path: path, code_coverage: false, scheme: “MyProject” testplan: "Localizations UI Tests" ) snapshot( concurrent_simulators: true, skip_open_summary: true, test_without_building: true, derived_data_path: path, only_testing: onlyTestingValue ) end
Де:
- build_for_testing: true — використовується для того, щоб збілдити застосунок у спеціальному режимі, який оптимізований для тестування;
- code_coverage: false- використовується для вказівки, чи потрібно збирати інформацію про покриття коду під час виконання тестів. Якщо ми ставимо false, Fastlane не генерує звіти про покриття коду. Це може зменшити час виконання тестів і знизити навантаження на систему;
- test_without_building: true — Fastlane не буде білдити застосунок перед виконанням тестів для знімків екрана.
Ця ідея була взята зі статті на medium, зменшила час з 6 годин до 3, що для тієї кількості тестів, яка проганялась, дуже і дуже хороший результат.
Висновки
Процес налаштування автоматизації тестів локалізацій може здатись громіздким. Однак коли він уже готовий і працює, відразу відчувається різниця між тим завантаженням застосунок знову й знову й перевірки руками тексту на екрані та переглядом зібраних в одному місці скриншотів цього екрану.
Також моя порада — не боятись того, що тести мають звичку «флекати». Дійсно, UI-тести вимагають постійного догляду. Але навіть це не перекриває того, скільки часу ви будете економити, переглядаючи результат UI-тестів без постійних перевстановлень застосунку, перемикань мови, а також повторення всього цього на різних девайсах.
На прикладі тесту, який дійсно є актуальним у моєму проєкті, я підрахувала, що за умов постійної його підтримки, на тестування локалізацій онбордингу я витрачаю не більше однієї години. Тест testOnboardingScreensSnapshot() перевіряє 7 екранів застосунку у двох орієнтаціях екрана одинадцятьма мовами щонайменше на трьох пристроях. Замість того, щоб видалити й завантажити застосунок руками 66 раз, я запускаю цей тест на СI, він проходить за 25 хвилин та 38 секунд, і після того я переглядаю скриншоти на правильність перекладу та форматування. Отже, можна сказати, що всі зусилля є виправданими.
Немає коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів