Привет! Давайте поговорим о том, как сейчас в 2020-ом году можно протестировать мультиязычное iOS приложение, если не хочется проверять локализацию вручную.  Мы — финтех-компания, и наша прибыль напрямую зависит от объема торгов. Чем больше наши клиенты открывают ордеров (или сделок), тем больше прибыли получаем мы. А количество сделок напрямую зависит от депозитов: чем больше депозитов, тем больше ордеров может открыть клиент и, соответственно, на больший объем. Но в разных странах удельный размер депозитов сильно различается. Например, в Таиланде в четыре раза больше пользователей, чем во Вьетнаме, но по депозитам этого не скажешь.  И некоторое время назад наш Product Owner подумал о причинах. Оказалось, что на это влияет отсутствие локализации — интерфейс приложения на тайском языке, а названия местных тайских банков отображались на английском.  Мы провели эксперимент: перевели названия, и после релиза количество депозитов возросло в разы.  То есть перевод 6-7 строчек текста на нужный язык в нужном месте может принести очень большую выгоду. Немного расскажу о нашем приложении Exness Mobile Trader.  Это личный кабинет трейдера, в котором мы сделали свой собственный торговый терминал на WebSocket. Приложение умеет работать с большим количеством международных и региональных платёжных систем. Есть много преднастроенных сервисов, которые позволяют пользователю торговать эффективнее. Также приложение поддерживает несколько типов счетов: реальные счета с настоящими деньгами, демо-счета для тренировки и крипту. Вишенка на нашем торте — это гибкая система push-уведомлений.  Всё это есть в приложении сейчас, но так было не всегда. История началась около двух лет назад.  В начале 2017 года мы поняли, что 70% пользователей нашей торговой системы заходят в свой личный веб-кабинет через мобильные браузеры, то есть через телефоны и планшеты. И мы решили создать свое мобильное приложение. Сделали пробную версию, но она оказалась неудачной. А в сентябре 2017-го начали работать над основным приложением. Работа заняла год. Мы экспериментировали. Например, в тестировании пробовали BDD-подход, автоматизировали API в Postman. К сентябрю 2018 года был запланирован релиз, и где-то за пару месяцев до этого встал вопрос локализации, так как у нас очень много клиентов по всему миру.  Локализация — это процесс адаптации и интернационализации под конкретный регион. Добавление специализированных компонентов, характерных для определенной локали, и перевод текста.  Нашим решением стал Crowdin — система управления мультиязычным контентом. Это огромный комбайн, в котором как в Jira, заводишь задачи и распределяешь по всевозможным исполнителям: переводчикам, менеджерам, тестировщикам. Можно голосовать за понравившийся перевод, оставлять комментарии. Благодаря этому инструменту мы довольно быстро перевели приложение на разные языки. Crowdin всеядный: ему можно скормить XML, так YML, JSON, строки. Он всё распарсит и будет с этим работать. Он не позволяет переводчикам что-то поломать: мухи строки отдельно, код отдельно. У Crowdin крутой API. Можно чуть ли на каждый коммит повесить создание новой задачи на перевод. Инструмент очень гибкий, позволяет работать как с целым файлом для какого-то языка под определенную фичу, так и с отдельными строками. Можно быстро посмотреть, как эта строчка переведена на родственные языки, это актуально, например, для китайского.  А недостаток Crowdin в его довольно высокой стоимости.  После перевода возникла новая проблема: как всё это быстро загрузить и проверить?
В этом помог LinguanApp — «умный» редактор строк в Xcode-проекте, отображающий их в более-менее удобоваримом виде. Он позволяет посмотреть, как выглядят строчки на разных языках, и тут же что-то подправить. Реализована обратная совместимость: сделанные изменения отображаются во взаимосвязанных локациях. В LinguanApp есть удобная функция автоматической валидации: после загрузки всех данных система проверяет, где и что не подгрузилось. Благодаря знакомому, экселеподобному интерфейсу, этот инструмент отлично подходит для управления процессом локализации.  Недостатки: LinguanApp тоже стоит денег, но не так много, как Crowdin.  Итак, переводы загружены. Нужно проверить, как это выглядит на устройствах, протестировать локализацию — убедиться, что приложение ведет себя так же, как до локализации. В нашем случае тестирование локализации заключается в проверке, что все строки одной локали переведены. Не менее важно, чтобы все слова и фразы помещались в отведенные места в интерфейсе, ничто не накладывалось друг на друга и не обрезалось:  Какие сложности могут возникнуть при ручном тестировании локализации? Чтобы пользователю было удобно пользоваться вашим приложением, нужно проверить, как оно выглядит при всех поддерживаемых вами разрешениях экранов и на всех языках, которых может быть очень много: наше приложение кроме английского переведено еще на 14 языков. У одного только iPhone сейчас шесть размеров экранов (если говорить о телефонах, начиная от iPhone SE), итого 6 х 15 = 90 комбинаций «язык — разрешение». Вручную это проверить практически нереально. Хотя изначально мы выпустили приложение только на двух языках, так что протестировать его вручную ещё можно было. Но даже тогда у нас возникли трудности. Во-первых, у нас не было всех видов устройств: на момент релиза приложения ещё отсутствовал в продаже iPhone Xs Max, а в наличии у нас были только iPhone X, 6S и 6 plus. Конечно, можно было пользоваться симулятором, но это не очень хороший вариант. Мы решили воспользоваться функцией Display Zoom:  В чем суть? Вы можете задать у себя разрешение другого смартфона, картинка и текст станут крупнее. Эта функция появилась в iPhone 6S, который в режиме Display Zoom переходил в разрешение iPhone 5. В iOS 11 и iPhone Xs нет Display Zoom, а в Xr и Xs Max он работает одинаково.  Нам не понравилось тестировать вручную. Неплохо было бы это автоматизировать. Точнее, автоматически создавать скриншоты.  Сначала необходимо подготовить тесты. Проблема в том, что универсального решения не существует. То есть для автоматического создания скриншота вам нужен UI-тест. Однако традиционные UI-тесты, которые завязаны на элементы интерфейса, при смене локалей будут падать. Чтобы этого не происходило, нужно добавить элементам интерфейса accessebility Identifier’ы:  Сделать это можно как в коде, так и в Identity Inspector в Xcode.  Для демонстрации инструментов, о которых пойдет речь дальше, я подготовил простой тест. Он написан на Swift с помощью XCTest. Запускается приложение, нажимаем кнопку Tutorial. После её исчезновения делаем скриншот. Потом в цикле смахиваем влево, каждый раз делая скриншот. Закрываем tutorial и ждем, чтобы кнопка исчезла. Вот как это выглядит на симуляторе: Это скопированный со Stackoverflow метод создания скриншотов: Это метод ожидания исчезновения элемента: Есть два способа автоматического создания скриншотов на основе теста. Первый — с помощью Fastlane, здоровенного комбайна для автоматизации рутинных задач. Он умеет делать очень много чего, включая конфигурирование и запуск тестов. Настроить его не сложно:  По итогу создадутся два файла SnapshotHelper.swift и Snapfile, мы к ним еще вернемся.
Потом нужно будет для Fastlane создать UI Test Target. Берем UI Test Bundle:   Указываем цель для тестирования:   Потом обязательно указываем Target membership и переносим туда созданные файлы.
Теперь мы создаем для целевого объекта новую схему. Убеждаемся, что у неё свойство shared. Удостоверяемся, что секция Build выглядит примерно так:  а Test вот так:  Теперь нам нужно инициализировать Fastlane с помощью функции setUp(), которая исполняется перед каждым запуском вашего тест-класса:  И нужно будет немного поменять тест, который мы недавно написали, чтобы инициализировать вызов метода создания скриншотов в Fastlane. А именно заменить функцию makeScreenshot() на snapshot("snapshot_name"), то есть Fastlane позволяет заранее настраивать названия скриншотов. Плюс надо немного переписать цикл, чтобы у каждого скриншота было уникальное имя. Вот что у нас получилось: Для демонстрации давайте ограничимся 4 размерами экранов и 5 языками: русским, китайским, тайским, вьетнамским и корейским. Чтобы задать эти параметры, нам нужно настроить снэпшот файл — по сути своей, конфиг. Он выглядит так:  В list of device мы перечисляем устройства, для которых будем делать скриншоты; также указываем языки и схему, которая содержит UI-тесты, которые мы создали. Затем указываем место, куда будут сохраняться скриншоты, и задаём порядок действий с предыдущими скриншотами — нужно ли их удалять.  После запуска командой fastlane snapshot мы получаем HTML-страницу со скриншотами, которые можно группировать как по языку, так и по типу экрана.  Однако недостатком Fastlane является низкая скорость работы. Даже для нашего маленького проекта из 5 страниц программа генерировала скриншоты под 4 разрешения и на 5 языках целых 28 минут. А на реальном проекте уходили часы. Поэтому мы отказались от Fastlane.  Ещё один инструмент для автоматического создания скриншотов — XCTest Plan. Его представили на WWDC 2019 вместе с Xcode 11. Новая функциональность позволяет тестировщикам и разработчикам конфигурировать тесты согласно своим потребностям: определять, какие тесты запускать в сборке и в каком порядке, что делать с артефактами. И самое главное, XCTest Plan позволяет нам относительно безболезненно и быстро создавать скриншоты прямо внутри Xcode без внешних зависимостей.  Давайте пробежимся по настройке. Прежде всего, нужно создать схему для последующей конвертации, чтобы можно было использовать XCTest Plan:   После конвертации получим конфигурационный файл:  И теперь нужно сделать по копии для каждого из выбранных языков. Эти копии будут отличаться лишь строкой application language:  В Shared settings мы оставляем system language, это, в нашем случае, английский язык:  Теперь остается только запустить тест. Это можно сделать двумя способами:  правой кнопкой —> Run yourTestName():  причем можно запустить тест как во всех конфигурациях сразу, так и отдельно для каждого языка  и командой Xcodebuild: Здесь мы указываем рабочее пространство, схему, testPlan и destination. Можно не только выбрать разные модели телефонов, но и задать им разные версии iOS, таким образом решив проблему фрагментации операционных систем.  Давайте запустим наш XCTest Plan. Он для четырёх симуляторов поочерёдно меняет локали и делает скриншот каждой страницы, а в конце удаляет симуляторы. Работает намного быстрее Fastlane.  На выходе мы получаем отчёт testTutorial:  Здесь проявляется один из недостатков XCTest Plan: смотреть скриншоты довольно неудобно. В Fastlane создаётся HTML-страница, в которой можно группировать скриншоты, а здесь каждый раз приходится нажимать на предпросмотр. Насколько мне известно, Xcode позволяет экспортировать изображения, но по какой-то причине мне это сделать не удалось. Либо мой XCTest Plan не так настроен, либо это баг Xcode.  В целом же это очень крутой нативный инструмент. Я надеюсь, что Apple будет его в дальнейшем поддерживать, развивать, править возникающие баги. То, что Fastlane сделал за 28 минут, XCTest Plan сделал за 2,6 минуты. То есть в 10 раз быстрее.  Мы получили скриншоты, теперь нужно их сравнить. Это можно сделать автоматически, так называемое снэпшот-тестирование: при первом запуске теста создаётся некое эталонное изображение, с которым будут сравниваться скриншоты при всех последующих запусках.
Среди бесплатных инструментов для снэпшот-тестирования хочу отметить два фреймворка: Первый — это iOS Snapshot Test Case, библиотека, написанная на Objective C. Она преобразует UIView/CALayer в изображения и сравнивает их. При первом запуске записываем (recordMode = true) референс (эталон). При последующих запусках (recordMode = false), сравниваем полученные скриншоты с эталоном.   Здесь на второй картинке сместились логотипы и фраза Deposit is in your account! Long title! Test it Elon Musk!, а также изменился цвет надписи 120 000 000.00 USD. То есть сразу видно, в чем проблема. Этот фреймворк довольно гибко настраивается. Мы можем поставить заплатки вместо динамических элементов, из-за изменения которых тесты падают. Например, можно сделать заплатку вместо transaction id, который уникальный в каждом тесте.  Обратите внимание на отсутствие Статус бара (панели с часами). Мы её отрезали, потому что время меняется, и тест падает.  Для настройки фреймворка нужно указать pod   и прописать две переменные окружения: FB_REFERENCE_IMAGE_DIR — куда кладётся эталон, и
IMAGE_DIF_DIR — куда будут складываться дифы при сбое теста.  Одним из достоинств фреймворка является визуализация различий между скриншотами. Также он по-человечески именует скриншоты, красиво раскладывает их по папкам. Почему это важно, вы поймете, когда я расскажу о следующем фреймворке. Второй фреймворк — это Swift Snapshot Testing, он создан в Point Free Co. Работает по тому же принципу: записывает эталон и сравнивает с ним. Фреймворк принимает и JSON, и дампы, и URL, практически что угодно.  Настраивается он тоже довольно просто. Eсли импортировать модуль Import Snapshot Testing, то в функции assertSnapshot() мы будем сравнивать ViewController как изображение. При первом запуске фреймворк создаст эталон и будет сообщает о несовпадениях с ним при всех последующих запусках.  К достоинствам инструмента можно отнести то, что он написан на Swift и всеяден. Главный недостаток — отсутствие diff: фреймворк лишь сообщает о самом факте несовпадения. плюс бардак с наименованием и размещением скриншотов. Допилить его можно, благо, что он open source, но из коробки работает не так, как бы нам хотелось.  Мы поговорили о двух инструментах, которые упрощают выгрузку и проверку локализованных текстов для приложений. Затем рассмотрели два фреймворка — новый XCTest Plan и старый Fastlane. С их помощью можно автоматически создавать скриншоты интерфейса. И в заключение рассмотрели два инструмента для снэпшот-тестирования.  У нас в Exness сейчас два мобильных проекта: Exness Trading, личный кабинет трейдера, и Social Trading, позволяющий просто положить деньги и копировать сделки других более опытных трейдеров. Crowdin у нас до сих пор используется в обоих проектах. От LinguanApp в Exness Trading мы отказались, потому что у нас довольно много плейсхолдеров, которые актуальны только для одной локали, а автоматическая валидация постоянно давала сбои. Зато этот инструмент продолжает использовать команда Social Trading. Также в этом проекте используется Fastlane для создания и загрузки билдов. С XCTest Plan мы экспериментируем в обоих приложениях. Наконец, в Exness Trading мы внедряем Swift Snapshot Test Case, а в Social Trading — iOS Snapshot Test Case. Спасибо что дочитали до конца, надеюсь эта статья будет вам полезной. Happy testing! https://agostini.tech/2018/07/15/automatic-screenshots-with-fastlane-snapshot/
https://docs.fastlane.tools/getting-started/ios/screenshots/ https://shashikantjagtap.net/wwdc19-getting-started-with-test-plan-for-xctest/
https://developer.apple.com/videos/play/wwdc2019/403
https://developer.apple.com/videos/play/wwdc2019/413 https://github.com/pointfreeco/swift-snapshot-testing/ https://github.com/uber/ios-snapshot-test-case/ http://www.iphonehacks.com/2014/09/use-display-zoom-iphone-6-plus.html Mobile QA engineer