1. Unit + UI testing
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
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
формате - удобно редактировать.
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);
}];
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];