- PVSM.RU - https://www.pvsm.ru -
С недавних пор мы взялись за внедрение UI-тестирования в iOS для iFunny. Путь этот тернист, долог и холиварен. Но все равно хочется поделиться с умными людьми своими первыми шагами в этом направлении. На истину не претендуем – всё примеряли к собственному продукту. Поэтому под катом немного информации о том, что такое iFunny на iOS и зачем нам понадобился UI + много фидбека по инструментам и примеров кода.
iFunny — это популярное в США приложение про юмор и мемы с ежемесячной аудиторией в 10М. Подробнее о том, как все затевалось, можно прочитать здесь [1]. Разработка приложения на iOS стартовала 6 лет назад, и мы до сих пор обходимся без каких-либо революционных вкраплений:
У нас все наоборот: работать — значит мемы смотреть :)
Unit-тесты мы используем для критичных моментов бизнес-логики и Workarounds.
Вот довольно простой тест: тестируем метод нашей модели, который проверяет поступление к нему нового контента.
- (void)testIsNewFeaturedSetForContentArrayFalse {
FNContentFeedDataSource *feedDataSource = [FNContentFeedDataSource new];
NSMutableArray *insertArray = [NSMutableArray arrayWithArray:[self baseContentArray]];
feedDataSource.currentSessionCID = @"0";
BOOL result = [feedDataSource isNewFeaturedSetForContentArray:insertArray];
XCTAssertFalse(result, @"cid check assert");
feedDataSource.currentSessionCID = @"777";
result = [feedDataSource isNewFeaturedSetForContentArray:insertArray];
XCTAssertTrue(result, @"cid check assert");
}
Второй класс тестов, который мы используем, – это тесты, которые необходимы, чтобы проверить правила переопределения классов. В один момент нам понадобилось написать много однотипных классов для системы аналитики, отличающихся набором статичных методов.
Xcode и Objective-С не давали какого-либо решения для защиты от неправильно написанного кода.
Поэтому мы написали такой тест:
- (void)testAllAnalyticParametersClasses {
NSArray *parameterClasses = [FNTestUtils classesForClassesOfType:[FNAnalyticParameter class]];
for (Class parameterClass in parameterClasses) {
FNAnalyticParameter *parameter = [parameterClass value:@"TEST_VALUE"];
XCTAssertNotNil(((FNAnalyticParameter *)parameter).key);
XCTAssertNotNil(((FNAnalyticParameter *)parameter).dictionary);
}
}
Здесь проверяется, что у класса определены 2 статичных метода, key и dictionary, необходимых для правильной работы отправки событий в системы аналитики.
Мы уже достаточно хорошо изучили работу с UI-элементами и поразмышляли над тестовым окружением в процессе написания тестов для Android. Получилось примерно так:
На поток поставить не получилось, так как занимались разработкой новой версии и ждали, когда все утрясется. Сейчас активно восстанавливаем и нарабатываем тестовую базу.
Пришла очередь iOS, и мы, команда QA и iOS-разработчики, начали с того, что еще раз собрались и аргументировали для себя, зачем нам нужны автотесты. Это был важный ритуал, и действовал он почти как мантра:
Начали с выбора инструментов. На повестке было 3 основных фреймворка, которые сейчас чаще всего используются для тестирования мобильных приложений. Мы примерили каждый из них.
Appium – популярный кроссплатформенный фреймворк. Бытует мнение, что именно он станет стандартом в тестировании мобильных приложений в ближайшем будущем. Несколько месяцев назад мы решили потестить его как с полгода вышедшей iOS 10, но немного огорчились: версия Appium с ее поддержкой была в бете, а использовать в проде нестабильную версию не очень хотелось. Appium Inspector, который работает на Android, тоже использовать не смогли: не было поддержки Xcode 8 и iOS 10. Вскоре они выпустили stable-версию, но ждать полгода после обновления оси для нас крайне нежелательно. Решили не мучить ни себя, ни Appium.
Calabash – кроссплатформенное open source решение, которое использует подход BDD в написании тестов и до последнего времени поддерживалось компанией Xamarin. Недавно разработчики сообщили, что поддержка – всё. Мы тоже решили дальше не идти.
И, наконец, XCTest – нативный фреймворк от Apple, который мы в итоге выбрали. Поэтому почитайте про плюсы:
Потом рассмотрели еще и Recorder — нативный инструмент от Apple, который позиционируется как вспомогательный, без надежды на то, что он будет использоваться при написании реальных тестов. С его помощью можно изучить лейблы UI-элементов и поиграться с основными жестами. Recoder сам пишет код и генерирует указатели на объекты, если это не было сделано при разработке. Это единственное преимущество, которое мы смогли выделить. Минусов оказалось гораздо больше:
А теперь про проблемы, с которыми столкнулись на практике: их мы будем решать, привлекая разработку.
Плюс черного ящика оборачивается в минус: мы не можем знать о текущем состоянии приложения ни на девайсе, ни на симуляторе. Нам необходимо его обнулить и создать по аналогии с Android определенную среду, где приложению сообщается, в какой стране мы работаем и с какими пользователями хотим взаимодействовать. Все это решается с помощью настроек запуска приложения.
Также нам понадобились pre-action в Xcode.
Для того, чтобы сбрасывать рабочую среду перед каждым тестом, мы решили удалять с симулятора установленное приложение, чтобы обнулить настройки пользователя и все, что сохранено в песочнице:
xcrun simctl uninstall booted ${PRODUCT_BUNDLE_IDENTIFIER}
C Environment-переменными мы работаем так:
app = [[XCUIApplication alloc] init];
app.launchEnvironment = @{
testEnviromentUserToken : @"",
testEnviromentDeviceID : @"",
testEnviromentCountry : @""
};
app.launchArguments = @[testArgumentNotClearStart];
В тесте создается объект приложения и в поля launchEnviroment и launchArguments записывается словарь (или массив) с настройками, которые нужно передать в приложение.
В приложении настройки и аргументы считываются в делегате при самом старте приложения в методе:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
Так у нас выполняется обработка:
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
[FNTestAPIEnviromentHandler handleArguments:processInfo.arguments
enviroment:processInfo.environment];
Класс TestAPIEnvHandler реализует обработку словаря настроек и массива аргументов.
Когда мы начали работать с ХСТest для UI, возникла проблема: стандартный набор инструментов не дает считывать шрифты и цвета.
Мы можем работать только с жестами для элементов, но не можем читать текст, который в них записан, брать их позицию или другие интересные для UI-тестирования свойства.
После поиска альтернативных решений мы посмотрели в сторону Accessibility API, с помощью которого работают UI-тесты.
В качестве “моста” между тестом и приложением решили использовать accessibilityValue, который есть у каждого видимого элемента из iOS SDK.
Поехал велосипед, и получилось такое решение:
Вот пример для UIButton:
@implementation UIButton (TestApi)
- (NSString *)accessibilityValue {
NSMutableDictionary *result = [NSMutableDictionary new];
UIColor *titleColor = [self titleColorForState:UIControlStateNormal];
CGColorRef cgColor = titleColor.CGColor;
CIColor *ciColor = [CIColor colorWithCGColor:cgColor];
NSString *colorString = ciColor.stringRepresentation;
if (titleColor) {
[result setObject:colorString forKey:testKeyTextColor];
}
return [FNTestAPIParametersParser encodeDictionary:result];
}
@end
Чтобы прочитать accessibilityValue в тесте нужно обратиться к ней, для этого у каждого объекта XCUElement есть поле value:
XCUIElement *button = app.buttons[@"FeedSmile"];
NSData *stringData = [button.value dataUsingEncoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:stringData options:0 error:&error];
Проблема жестов и экшенов решается (о чудо!) самим инструментом, благодаря большому набору стандартных методов – tap, double tap. Но в нашем приложении есть не только стандартные, но и очень нетривиальные вещи. Например triple tap, свайпы по всем осям в разные стороны. Чтобы это решить, мы использовали те же стандартные методы, конфигурируя параметры. Большой занозой это не оказалось.
Пример простого теста с использованием подхода:
- (void)testExample {
XCUIElement *feedElement = app.otherElements[@"FeedContentItem"];
XCTAssertNotNil(feedElement);
XCUIElement *button = app.buttons[@"FeedSmile"];
[button tap];
[[[[XCUIApplication alloc] init].otherElements[@"FeedContentItem"].scrollViews childrenMatchingType:XCUIElementTypeImage].element tap];
NSDictionary *result = [FNTestAPIParametersParser decodeString:button.value];
CIColor *color = [CIColor colorWithString:result[testKeyTextColor]];
XCTAssertFalse(color.red - 1.f < FLT_EPSILON &&
color.green - 0.76f < FLT_EPSILON &&
color.blue - 0.29f < FLT_EPSILON,
@"Color not valid");
XCUIElement *feed = app.scrollViews[@"FeedContentFeed"];
[feed swipeLeft];
[feed swipeLeft];
[feed swipeLeft];
}
Мы не планировали делать полное тестовое покрытие, поэтому на этом наши эксперименты пока закончились. Стало ясно, что если мы когда-то решимся полноценно внедрить автотесты в процесс, использовать будем XCtest, но сейчас заниматься этим на постоянной основе очень трудозатратно. И вот почему:
P.S. При съёмке превью ни один баг не пострадал. Семён продолжает вдохновлять команду QA.
Автор: FunCorp
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/ios/262020
Ссылки в тексте:
[1] здесь: http://habrahabr.ru/company/funcorp/blog/321508/
[2] Источник: https://habrahabr.ru/post/335328/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.