- PVSM.RU - https://www.pvsm.ru -
Людей, которые не просто хотя бы раз писали UI-тесты, а делали бы это в коммерческих проектах, довольно мало, потому что эту часть разработки очень сложно продать заказчику и аргументировать менеджеру, зачем они нужны и почему занимают столько времени. Новосибирская компания Improve Digital [1] решилась на этот шаг по ряду причин, в частности из-за того, что разрабатываемый проект долгосрочный и с большим потенциалом дальнейшего развития.
Далее расшифровка выступления Михаила Домрачева на AppsConf [2] 2017, в ходе которого он рассказал, как на практике внедрить UI-тесты в iOS проектах, и поделился мыслями, когда это действительно необходимо, а когда — излишне.
Забегая вперед, отметим, что тут есть и плюсы, и минусы. Но, на наш взгляд, существенное уменьшение количества дизайн-багов без огромных трудозатрат ручного тестирования — неоспоримое преимущество, которое любого должно примерить с небольшими возникающими трудностями.

Начнем издалека, с описания проекта до того, как мы столкнулись с проблемами с дизайном.
Мы работаем с довольно большой зарубежной финансовой компанией, у которой есть филиалы в разных странах. В каждой стране есть свое мобильное приложение, которое имеет общие функции, но отличается дизайном и некоторой специфичностью для страны, связанной, например, с законодательством.
Все наши экраны реализованы в табличном виде. Таблица состоит из набора таких связок, как ячейка и View-Model для нее. То есть мы можем настроить контент ячейки через View-Model и выставить определенный дизайн, в разных странах она будет выглядеть по-разному, но по сути это один и тот же ресурс, который лежит в Core framework.

На рисунке показан экран. В верхней ячейке есть своя View-Model, через которую можно:
Во View-Model для кнопки можно выставить:
В среднем в каждом новом приложении у нас переиспользуется примерно 70% функциональности из Core framework и только 30% накручивается чего-то нового.

В Core framework находится:
В каждом новом приложении нам приходится добавлять следующее:
Хотя обычно все ячейки берутся из Core. Их дизайн мы можем настроить через View-Model, они довольно универсальные, и на выходе мы получаем дизайн в виде Лего.

В результате у нас получается такой «Франкенштейн».

Это один и тот же экран, но в разных странах. Он имеет общую базовую реализацию, то есть запрограммирован в Core. Отличия в дизайне мы реализуем через различные категории для цветов и картинок. Если приглядеться, тут с трудом можно найти 10 отличий, например:

На втором типе экрана уже посложнее. Слева — базовая реализация в Core framework, справа — заказчик в новой стране решил немного по-другому отображать ту же самую информацию. Бизнес-логика сохранилась, но надо было сделать новый дизайн. Мы использовали другие ячейки (тоже из Core framework), и также реализовали новую функциональность — оплату кредита банковской картой.
Все вроде было хорошо, заказчик был очень доволен и предложил увеличить обороты и делать новые страны быстрее. Но, естественно, мы не все учли.
Чтобы увеличить обороты на проекте, были наняты новые разработчики.
Как уже говорил, у нас общие ресурсы для всех стран, и они в открытом доступе. Новый разработчик (или даже старый) мог модифицировать параметры или двигать/менять элементы в общих ресурсах. Все, конечно, понимают, что так нельзя делать, но все равно бывали случаи.
Все наши приложения имеют единый стиль, например, одинаковое выравнивание текста, определенные цвета ячеек, одинаковые фоны. Со временем, наш дизайнер начал забывать про этот единый стиль и мог внести изменения в стиль базовой ячейки. Но он даже не подозревал, что тем самым может навредить совсем другому приложению, за которое он даже не в ответе. А разработчик, который им занимается, тоже не знал, что ему навредили. В итоге при ручном тестировании постоянно находились какие-то проблемы с «уехавшим» дизайном.
Мы сразу поняли, что решением этих проблем будет внедрение Ul-тестов. Перед тем, как внедрять Ul-тесты, мы сделали небольшой анализ рынка инструментов, с помощью которых это можно делать именно на iOS проектах, другие платформы мы не рассматривали.

Мы сделали анализ по четким критериям:
У Apple и Appium есть такие инструменты, а у Calaba.sh в базовой комплектации нет, но есть Xamarin cloud, где эту функцию можно купить. Также там предоставляется более 2000 девайсов, на которых можно прогонять тесты. Test-recording — не всегда очень хорошо. Когда мы руками ничего не пишем, мы иногда не понимаем, что генерируется. Отсутствие такого инструмента может сыграть и в плюс. Об этом я расскажу чуть позже.
4. Опыт использования членами нашей команды какого-либо инструмента до нашего проекта.
Все ребята в нашей команде использовали только один инструмент — Apple. Точнее, просто что-то знали про него. Все единогласно проголосовали за этот инструмент. В принципе, нам хватало тех функций, которые он дает, другие платформы нам были не так важны.
Расскажу про него немного поподробнее.
iOS Ul testing — довольно молодая технология, которая была представлена в 2015 году. Она основывается на двух вещах:
XCTest базируется всего на трех классах:
Все просто — ставим курсор в функцию, нажимаем record. Запускается UI-тест. Мы нажимаем на элементы и генерируется код.
Посмотрим, что он сгенерировал.
XCUIApplication *app = app2;
[app.buttons[@"start"] tap];
XCUIElement *element = [[[[app.otherElements
containingType:XCUIElementTypeNavigationBar identifier:@“UIView”]
childrenMatchingType:XCUIElementTypeOther].element
childrenMatchingType:XCUIElementTypeOther].element
childrenMatchingType:XCUIElementTypeOther].element;
[element tap];
XCUIApplication *app2 = app;
[app2.keys[@"t"] tap];
[app typeText:@“t"];
. . .
[app typeText:@"c"];
[app2.keys[@“o"]
[element tap];
Конечно, не всегда он делает так плохо, но я честно попытался 3-4 раза записать демо, и всегда натыкался на одни и те же ошибки:
Эти минусы UI test recording не всегда встречаются, но при написании UI-тестов важна скорость. Хочется быстро, красиво написать какие-нибудь функции, и при этом не хочется возвращаться и что-то править после test recording. Обычно это занимает больше времени, чем сразу написать самому.
Первое, с чем мы столкнулись, это Stub manager, вернее, с его отсутствием. До этого у нас на проекте не были внедрены unit-тесты, и мы никак не эмулировали связь с сетью.
Мы хотели не повторять ошибок test-recording и писать тесты более чистыми и правильными. В этом нам помог Page object pattern. Про него мало кто знает, но тестировщики, которые пишут тесты, должны быть в курсе.
Мы внедрили инструмент Snapshot для получения скриншотов во время выполнения наших UI-тестов, чтобы не только видеть, упал ли наш тест при его выполнении, но и смотреть результат, сравнивать дизайн на экранах.
Рассмотрим подробно каждый пункт.
Почему-то считается, что реализовать эмуляцию сети сложно. На самом деле все делается очень просто с помощью class NSURLProtocol. Это такой класс, который позволяет предопределить работу системы загрузки URL для iOS.
Все делается в два действия:
Когда в приложении вы отправляете запрос в сеть, система загрузки сначала проверяет, зарегистрированы ли какие-либо свои NSURLProtocol’ы. Если да, то она к каждому из них в своем порядке обращается и спрашивает, возможно ли обработать данный запрос. Если мы говорим да, то запрос в сеть обрывается, и мы обрабатываем его на уровне приложения.
Нужно переопределить всего три метода:
+(BOOL)canInitWithRequest:(NSURLRequest *)request
-(void)startLoading
-(void)stopLoading
Немного подробнее про startLoading, где происходит вся магия.
(void)startLoading
NSData *cachedData
if (cachedData) {
NSHTTPURLResponse *response
[self.client URLProtocol: didReceiveResponse: cacheStoragePolicy:];
[self.client URLProtocol:self didLoadData: cachedData];
[self.client URLProtocolDidFinishLoading: self];
} else {
[self.client URLProtocol: self
didFailWithError: создаем ошибку
}
Внутри мы просто берем данные из кэша (NSData *cachedData). В нашем случае они хранятся в разных бандлах, потому что для каждого тест-кейса на один и тот же запрос могут быть разные ответы. Если такие данные есть, создаем свой кастомный response, отдаем его системе, и также говорим, чтобы она вернула закэшированные данные.
Если же мы не нашли этих данных, мы создаем просто свою кастомную ошибку, которая вернется на запрос, как будто она пришли из сети.
В Stub manager вы только регистрируете свой класс. Это делается через:
NSURLSessionConfiguration setProtocolClasses: @[[MyNSURLProtocol class]]
Все делается очень быстро, легко и работает. Так что не надо этого бояться.
Как я говорил, мы решили побороться с минусами test-recording и более ответственно отнестись к написанию UI-тестов. Мы попытались сделать код более чистым, и применили паттерн Page object pattern.

Он довольно простой и основывается на том, что между тестами и экраном, для которого пишутся тесты, должна быть какая-то прослойка, где реализована переисиользуемая функциональность. То есть там зашиты все сложные взаимодействия с элементами и все поиски элементов на экранах.
Польза от Page object pattern:
Мы решили, что будет излишним писать прослойку для каждого экрана. Мы написали просто общую прослойку для всего приложения, называется она XCTestUtils и состоит из 3 компонентов:
@interface FFElements : NSObject
-(XCUIElement *)objectForKeyedSubscript:(NSString *)key;
-(XCUIElement *)objectAtIndexedSubscript:(NSUInteger)index;
@end
@interface XCTestCase (Elements)
@property (nonatomic,readonly) FFElements *textFields;
@property (nonatomic,readonly) FFElements *buttons;
@property (nonatomic,readonly) FFElements *labels;
@property (nonatomic,readonly) FFElements *cells;
-(void)wait:(NSTimeInterval)interval;
@end
@interface XCUIElement (Utils)
@property (nonatomic) NSString *pasteText;
+(void)forceTap;
@end
Как вы видели в демо, чтобы ввести какой-то текст в textFields, генерируется код нажатия на каждую кнопку. Мы ввели функцию forceTap — длительное нажатие. Когда мы нажимаем на textFields, у нас появляется плашка «Вставить текст» либо «Скопировать».
Получилось круто, и все были довольны.
Покажу небольшое демо о том, как мы пользуемся Page object pattern.
Через property FFElements обращаемся к конкретным элементам с определенным типом и вызываем методы для взаимодействия с ними. С textFields работаем так: нажимаем, делаем длительное нажатие, и вставляем текст. Получилось всего 6 строк — понятных, читаемых, и, главное, работающих.
Мы запускаем UI-тест, он прогоняется на устройстве, а мы смотрим, чтобы дизайн не поехал. А можем и просмотреть.
Поэтому мы решили внедрить такой инструмент, как Snapshot. Он входит в группу инструментов Fastlane, он довольно простой, легко внедряется и от него большая польза.
Польза от Snapshot:
Для внедрения Snapshot требуется буквально три действия.
Мы можем указать вообще все девайсы, на которых будут прогоняться UI-тесты, скриншоты будут сделаны в одних и тех же местах на разных девайсах.
devices ([
"iPhone 6s"
])
scheme: "our scheme"
output_directory: "./path/."
stop_after_first_error: Bool
reinstall_app: Bool
clear_previous_screenshots: Bool
Erase_simulator: Bool
languages ([
"en-US"
])
Кроме того, указываем схему, на которой прогоняются UI-тесты, папку, где будет храниться результат всех скриншотов, и вспомогательные настройки.
Также мы можем указать языки, на которых будем прогонять наши UI-тесты. Если у вас много локализаций, вы можете тут указать все свои локализации и на выходе получить скриншоты с разными языками.
С технической точки зрения, в самом коде нужно просто добавить определенный файл — SnaphotHelper.swift в таргет с тестами. Этот файл генерируется в вашей папке с проектом, когда вы делаете fastlane snapshot init, его просто нужно переложить в таргет.
Далее взываем внутри метода setup(): [Snapshot setupSnapshot:app] внутри всех тест-кейсов, где хотим сделать скриншоты, и передать туда XCUI Application.
Последнее, что остается, просто в коде пробежаться по местам, где мы хотим сделать скриншот, указать имя скриншота, с которым сгенерируется картинка [Snapshot snapshot:@"Name screen"waitForLoadingIndicator:YES], запустить из терминала, сделать fastlane snapshot и радоваться.
После прохождения тестов там будут не только картинки разных экранов, но сгенерируется HTML страничка.

На ней все картинки разбиты по категориям, например, по девайсам и по локализации, которые вы указали в Snapfile. Правда, единственный минус — все эти скриншоты сортируются не по последовательности их выполнения, а по названию.
Перед тем, как вы решите внедрять UI-тесты, стоит обратить внимание на несколько моментов:
В нашем проекте мы получили следующие преимущества:
Я про это не говорил, но у нас довольно сложный роутинг. В разных странах он может отличаться. Мы просто на каждом новом экране проверяем специфичный элемент, который есть только на этом экране, и, если его нет, это значит, что мы находимся не в том месте, что-то пошло не так. Так мы проверяем роутинг.
Но мы также получили и небольшие минусы:
Как и обещал, в конце приведу свои мысли, когда же реально стоит внедрять UI-тесты.
Новости
В этом году мы решили вынести конференцию по мобильной разработке в отдельное мероприятие — теперь AppsConf Moscow [2] будет полностью обособленным мероприятием и пройдет 8 и 9 октября. Мы планируем провести самое большое мероприятие по мобильной разработке в России, собрать активистов всех сообществ мобильных разработчиков со всей страны, представить более 60 докладов для более чем 500 человек.
Но и на фестивале РИТ++ [3] будет много разносторонних смежных докладов, например, мы получили следующие заявки:
Mobile DevOps: автоматизируем и улучшаем процесс мобильной разработки [4] / Вячеслав Черников (Binwell)
Микросервисы на фронтенде в почте Mail.ru [5] / Егор Утробин (Mail.ru)
Интеграционное тестирование микросервисов на Scala [6] / Юрий Бадальянц (2ГИС)
Изучайте список заявок [7], с которым, правда, в течение месяца еще будет работать ПК, и, если заинтересовались, бронируйте билеты [8].
Автор: mi5ha6in
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/interfejsy/279477
Ссылки в тексте:
[1] Improve Digital: http://improve-group.ru/
[2] AppsConf: http://appsconf.ru/
[3] РИТ++: http://ritfest.ru/2018
[4] Mobile DevOps: автоматизируем и улучшаем процесс мобильной разработки: http://ritfest.ru/2018/abstracts/3431
[5] Микросервисы на фронтенде в почте Mail.ru: http://ritfest.ru/2018/abstracts/3397
[6] Интеграционное тестирование микросервисов на Scala: http://ritfest.ru/2018/abstracts/3348
[7] список заявок: http://ritfest.ru/2018/abstracts
[8] бронируйте билеты: http://conf.ontico.ru/conference/join/rit2018.html?popup=3
[9] Источник: https://habr.com/post/353276/?utm_source=habrahabr&utm_medium=rss&utm_campaign=353276
Нажмите здесь для печати.