Unit + UI testing
in iOS
Kirill Ushkov,
itomych studio
Зачем писать тесты?
• экономия времени (тестировщиков,
разработчиков)
• неожиданная плюшка в виде дополнительной
документации к коду
• раннее (а не заказчиком) обнаружение проблем,
далее см. пункт 1
Техники тестирования
• TDD - “разработка через тестирование”. Сначала
пишем тест на новое требование, убеждаемся, что
тест не проходит, потом пишем соответствующий
код таким образом, чтобы тесты проходили
• BDD - “разработка основанная на поведении”.
Ответвление TDD, основная фишка - тесты
оформляются “общедоступным
языком” (ubiquitous language). Примеры на iOS -
https://github.com/specta/specta, https://
github.com/kiwi-bdd/Kiwi
Метрики тестирования
• Покрытие кода тестами (code coverage) - мера
того, насколько хорошо протестирован Ваш код
Как то так это выглядит в
Xcode
Инструментарий юного
тестировщика
• Matcher - основной инструмент, помогает
сравнивать величины (https://github.com/hamcrest/
OCHamcrest, https://github.com/specta/expecta)
• Stub - заглушка, “заранее подготовленный”
эталон (http://ocmock.org, https://github.com/
AliSoftware/OHHTTPStubs)
• Mock - “макет” данных, эмуляция настоящих
данных для проверки взаимодействий (http://
ocmock.org)
Организация тестирования
• Перед тестированием определенного метода
необходимо четко определить какое поведение
Вы хотите проверить, какой “кейс” рассмотреть
• Для заглушек удобно использовать
заготовленные данные в json, plist или xml
формате - удобно редактировать.
Организация тестируемого
кода
• Тестируемость кода ухудшают неявные
зависимости: 



[[KNAPIClient sharedClient] userOfferWithSuccess:^(AFHTTPRequestOperation *operation, id
responseObject) {
KNUserOffer *offer = [KNUserOffer objectWithAttributes:responseObject];
KNUser *user = [KNDataManager sharedManager].appUser;
user.currentUserOffer = offer;
--wSelf.requestCounter;
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"User offer %@", error);
--wSelf.requestCounter;
}];
Dependency Injection
• Сначала перемещаем методы запросов в отдельный
протокол
• Далее объявляем в базовом контроллере:

@interface BaseViewController : UIViewController

@property (nonatomic) id<KNNetworkProtocol> apiClient;
@end
• Наконец объявляем геттер таким образом:

- (id<KNNetworkProtocol>)apiClient {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
#ifdef DEBUG
_apiClient = [KNLocalClient sharedClient];
#elif RELEASE
_apiClient = [KNAPIClient sharedClient];
#endif
});
return _apiClient;
}
XCTest
• Используется для тестирования кода в iOS
• Каждый класс, тестирующий отдельный модуль,
должен наследоваться от XCTestCase
• Каждый тестирующий метод в этом классе должен
начинаться со слова test. Можно избежать
выполнения теста если добавить в название метода
DISABLED_
• По умолчанию, все методы выполняются синхронно
XCTest
• Метод setup выполняется перед выполнением
каждого теста, а метода teardown - после
выполнения каждого теста
• Возможна отладка тестирующих методов
• Результаты каждой сессии тестирования
записываются в лог файл, путь к нему
прописывается в логах отладчика по окончанию
тестирования
Стоп, стоп, а как же
асинхронные методы?
• C iOS 8 появилось API для тестирования
асинхронного выполнения кода
XCTestExpectation:
NSString *email = @"unknownEmail@gmail.com";
XCTestExpectation *expectation = [self expectationWithDescription:@"Handler called"];
[self.apiClient forgotPasswordWithRecoveryEmail:email
withSuccessBlock:^(AFHTTPRequestOperation *operation, id response) {
expect(nil).toNot.beNil;
[expectation fulfill];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
expect(@(error.code)).to.equal(@(kCFURLErrorBadServerResponse));
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:self.expectationTimeOut handler:nil];
HTTP Stubs
• Если Вам необходимо проверить поведение
приложения при получении ответов от сервера,
то очень удобно использовать т.н. http stubs -
таким образом можно протестировать поведение
при “медленном” интернете или при его
отсутствии
OHHTTPStubs
• Основная идея такая - сначала Вы определяете
набор запросов по некоторым признакам, а затем
эмулируете ответ от сервера



OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest * _Nonnull request) {
return [request.HTTPMethod isEqualToString:@"POST"] && [request.URL.path
containsString:@"user"];
} withStubResponse:^OHHTTPStubsResponse * _Nonnull(NSURLRequest * _Nonnull request) {
return [OHHTTPStubsResponse responseWithFileAtPath:OHPathForFile(@"User.json", [self class])
statusCode:200 headers:headers];
}];
OHHTTPStubs:on stub
activation
• Также есть возможность отловить срабатывание
определенной “заглушки”:



[OHHTTPStubs onStubActivation:^(NSURLRequest *request, id<OHHTTPStubsDescriptor> stub) {
NSLog(@"[OHHTTPStubs] Request to %@ has been stubbed with %@", request.URL, stub.name);
}];
UI Testing
Тестирование UI
• Механика создания UI-тестов такая:
• Вначале “записываем” взаимодействие с UI
• Далее автоматически записываются наши
действия в виде кода
• Ставим нужные проверки внутри кода
• Прогонка теста
Тестирование UI
• Фича доступна с iOS 9, поэтому минимальный
таргет должен быть iOS 9, в противном случае не
будет доступна (или совсем отсутствовать кнопка
записи)
• Есть несколько основных классов:
XCUIElementQuery, XCUIApplication, XCUIElement
Expectations
• Есть возможность дождаться появления
некоторого элемента UI:



XCUIApplication *app = [[XCUIApplication alloc] init];
XCUIElement *label = app.staticTexts[@"Label"];
[self expectationForPredicate:[NSPredicate predicateWithFormat:@"exists == TRUE"]
evaluatedWithObject:label
handler:nil];
Demo
(https://github.com/muteKey/
WorkMailExample)
Полезное по unit testing
• https://github.com/specta/specta
• https://github.com/kiwi-bdd/Kiwi
• http://nshipster.com/xctestcase/
• https://www.objc.io/issues/15-testing/
• https://github.com/AliSoftware/OHHTTPStubs
• https://github.com/specta/expecta
• https://www.bignerdranch.com/blog/weve-got-you-covered/
• http://dou.ua/forums/topic/8897/
• https://habrahabr.ru/post/258953/
Полезное по UI testing
• https://youtu.be/58-Dfkt9HzU - неявные
зависимости, Dependency injection
• https://github.com/joemasilotti/UI-Testing-Cheat-
Sheet
• http://masilotti.com - блог о UI testing
Вопросы + контакты
• github: muteKey
• twitter: DeveloperKey

UI+unit testing in iOS

  • 1.
    Unit + UItesting in iOS Kirill Ushkov, itomych studio
  • 2.
    Зачем писать тесты? •экономия времени (тестировщиков, разработчиков) • неожиданная плюшка в виде дополнительной документации к коду • раннее (а не заказчиком) обнаружение проблем, далее см. пункт 1
  • 3.
    Техники тестирования • TDD- “разработка через тестирование”. Сначала пишем тест на новое требование, убеждаемся, что тест не проходит, потом пишем соответствующий код таким образом, чтобы тесты проходили • BDD - “разработка основанная на поведении”. Ответвление TDD, основная фишка - тесты оформляются “общедоступным языком” (ubiquitous language). Примеры на iOS - https://github.com/specta/specta, https:// github.com/kiwi-bdd/Kiwi
  • 4.
    Метрики тестирования • Покрытиекода тестами (code coverage) - мера того, насколько хорошо протестирован Ваш код
  • 5.
    Как то такэто выглядит в Xcode
  • 6.
    Инструментарий юного тестировщика • Matcher- основной инструмент, помогает сравнивать величины (https://github.com/hamcrest/ OCHamcrest, https://github.com/specta/expecta) • Stub - заглушка, “заранее подготовленный” эталон (http://ocmock.org, https://github.com/ AliSoftware/OHHTTPStubs) • Mock - “макет” данных, эмуляция настоящих данных для проверки взаимодействий (http:// ocmock.org)
  • 7.
    Организация тестирования • Передтестированием определенного метода необходимо четко определить какое поведение Вы хотите проверить, какой “кейс” рассмотреть • Для заглушек удобно использовать заготовленные данные в json, plist или xml формате - удобно редактировать.
  • 8.
    Организация тестируемого кода • Тестируемостькода ухудшают неявные зависимости: 
 
 [[KNAPIClient sharedClient] userOfferWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { KNUserOffer *offer = [KNUserOffer objectWithAttributes:responseObject]; KNUser *user = [KNDataManager sharedManager].appUser; user.currentUserOffer = offer; --wSelf.requestCounter; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(@"User offer %@", error); --wSelf.requestCounter; }];
  • 9.
    Dependency Injection • Сначалаперемещаем методы запросов в отдельный протокол • Далее объявляем в базовом контроллере:
 @interface BaseViewController : UIViewController
 @property (nonatomic) id<KNNetworkProtocol> apiClient; @end • Наконец объявляем геттер таким образом:
 - (id<KNNetworkProtocol>)apiClient { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ #ifdef DEBUG _apiClient = [KNLocalClient sharedClient]; #elif RELEASE _apiClient = [KNAPIClient sharedClient]; #endif }); return _apiClient; }
  • 10.
    XCTest • Используется длятестирования кода в iOS • Каждый класс, тестирующий отдельный модуль, должен наследоваться от XCTestCase • Каждый тестирующий метод в этом классе должен начинаться со слова test. Можно избежать выполнения теста если добавить в название метода DISABLED_ • По умолчанию, все методы выполняются синхронно
  • 11.
    XCTest • Метод setupвыполняется перед выполнением каждого теста, а метода teardown - после выполнения каждого теста • Возможна отладка тестирующих методов • Результаты каждой сессии тестирования записываются в лог файл, путь к нему прописывается в логах отладчика по окончанию тестирования
  • 12.
    Стоп, стоп, акак же асинхронные методы? • C iOS 8 появилось API для тестирования асинхронного выполнения кода XCTestExpectation: NSString *email = @"unknownEmail@gmail.com"; XCTestExpectation *expectation = [self expectationWithDescription:@"Handler called"]; [self.apiClient forgotPasswordWithRecoveryEmail:email withSuccessBlock:^(AFHTTPRequestOperation *operation, id response) { expect(nil).toNot.beNil; [expectation fulfill]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { expect(@(error.code)).to.equal(@(kCFURLErrorBadServerResponse)); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:self.expectationTimeOut handler:nil];
  • 13.
    HTTP Stubs • ЕслиВам необходимо проверить поведение приложения при получении ответов от сервера, то очень удобно использовать т.н. http stubs - таким образом можно протестировать поведение при “медленном” интернете или при его отсутствии
  • 14.
    OHHTTPStubs • Основная идеятакая - сначала Вы определяете набор запросов по некоторым признакам, а затем эмулируете ответ от сервера
 
 OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest * _Nonnull request) { return [request.HTTPMethod isEqualToString:@"POST"] && [request.URL.path containsString:@"user"]; } withStubResponse:^OHHTTPStubsResponse * _Nonnull(NSURLRequest * _Nonnull request) { return [OHHTTPStubsResponse responseWithFileAtPath:OHPathForFile(@"User.json", [self class]) statusCode:200 headers:headers]; }];
  • 15.
    OHHTTPStubs:on stub activation • Такжеесть возможность отловить срабатывание определенной “заглушки”:
 
 [OHHTTPStubs onStubActivation:^(NSURLRequest *request, id<OHHTTPStubsDescriptor> stub) { NSLog(@"[OHHTTPStubs] Request to %@ has been stubbed with %@", request.URL, stub.name); }];
  • 16.
  • 17.
    Тестирование UI • Механикасоздания UI-тестов такая: • Вначале “записываем” взаимодействие с UI • Далее автоматически записываются наши действия в виде кода • Ставим нужные проверки внутри кода • Прогонка теста
  • 18.
    Тестирование UI • Фичадоступна с iOS 9, поэтому минимальный таргет должен быть iOS 9, в противном случае не будет доступна (или совсем отсутствовать кнопка записи) • Есть несколько основных классов: XCUIElementQuery, XCUIApplication, XCUIElement
  • 19.
    Expectations • Есть возможностьдождаться появления некоторого элемента UI:
 
 XCUIApplication *app = [[XCUIApplication alloc] init]; XCUIElement *label = app.staticTexts[@"Label"]; [self expectationForPredicate:[NSPredicate predicateWithFormat:@"exists == TRUE"] evaluatedWithObject:label handler:nil];
  • 20.
  • 21.
    Полезное по unittesting • https://github.com/specta/specta • https://github.com/kiwi-bdd/Kiwi • http://nshipster.com/xctestcase/ • https://www.objc.io/issues/15-testing/ • https://github.com/AliSoftware/OHHTTPStubs • https://github.com/specta/expecta • https://www.bignerdranch.com/blog/weve-got-you-covered/ • http://dou.ua/forums/topic/8897/ • https://habrahabr.ru/post/258953/
  • 22.
    Полезное по UItesting • https://youtu.be/58-Dfkt9HzU - неявные зависимости, Dependency injection • https://github.com/joemasilotti/UI-Testing-Cheat- Sheet • http://masilotti.com - блог о UI testing
  • 23.
    Вопросы + контакты •github: muteKey • twitter: DeveloperKey