What's new?
В этой статье хочу рассказать о применении технологии BDD при разработке приложений под iOS.
Было интересно попробывать на практике одну из методологий: TDD или BDD. Выбор пал на BDD. Почему именно он? Очень интересно о нем рассказали на DevCamp'e в харьковском офисе Ciklum. Почему именно Kiwi? О нем также шла речь на этом пресловутом DevCamp'e. Поэтому хотелось все попробывать самому на практике. Так что, кому интересны примеры с BDD, немного сложнее, чем тестирование переворота строки или калькулятора, прошу под кат.
Постановка задачи
Какую задачу чаще всего решает разработчик бизнес-приложений под iOS? На мой взгляд, это получение данных с сервера и их отображение юзеру. Для примера было написано приложение, которое решало задачу получения вопросов по тагу «iphone» с сайта «StackOverflow» с последующим их отображением в таблице. Не буду углубляться в тонкости архитектуры и реализации, а лучше подробнее остановлюсь на интересных, по моему мнению, вещах.
Как протестировать запрос?
Наверное, у всех были ситуации, когда работая параллельно с web-командой, вы не могли достучаться до сервера. Причины этого могли быть разные, то ли url поменяли, то ли сервис сломался. В общем, не суть важно. А важно то, что было бы хорошо при запуске юнит тестов проверить, а отзывается ли наш сервер?! За общение с сервером отвечает класс «StackOverflowRequest». Нам надо, чтобы при успешном получении данных с сервера вызывался метод «receivedJSON:», а при неудачном — «fetchFailedWithError:». В качестве тестируемых данных выберем валидный и невалидный url.
it(@"should recieve receivedJSON", ^
{
NSString *questionsUrlString = @"http://api.stackoverflow.com/1.1/search?tagged=iphone&pagesize=20";
IFStackOverflowRequest *request = [[IFStackOverflowRequest alloc] initWithDelegate:controller urlString:questionsUrlString];
[[request fetchQestions] start];
[[[controller shouldEventuallyBeforeTimingOutAfter(3)] receive] receivedJSON:any()];
});
it(@"should recieve fetchFailedWithError", ^
{
NSString *fakeUrl = @"asda";
IFStackOverflowRequest *request = [[IFStackOverflowRequest alloc] initWithDelegate:controller urlString:fakeUrl];
[[request fetchQestions] start];
[[[controller shouldEventuallyBeforeTimingOutAfter(1)] receive] fetchFailedWithError:any()];
});
К сожалению, в данном тесте нарушается одно из условий тестирования: тесты должны проходить быстро. А так как мы ждем ответа от сервера, то это нехорошо. Поэтому я заключил эти 2 теста в блок «ifdef».
Как протестировать ожидание данных с сервера?
Как известно, если идет какая-то загрузка/обработка данных, то мы должны это показать пользователю, чтобы не было ощущения, что приложение зависло. Чаще всего это встречается при загрузке картинки с сервера. В моем случае я просто показываю спинер. Алгоритм, по которому я определяю загрузилась ли картинки, реализован с помощью KVO.
it(@"spiner should be visible during avatar loading", ^
{
IFQuestionCell *cell = (IFQuestionCell *)[tableDelegate tableView:[UITableView new] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
UIImageView *askerAvatar = (UIImageView *)[cell objectForPropertyName:@"askerAvatar"];
[askerAvatar.image shouldBeNil];
UIActivityIndicatorView *spiner = (UIActivityIndicatorView *)[cell objectForPropertyName:@"spiner"];
[spiner shouldNotBeNil];
[[theValue(spiner.hidden) should] equal:theValue(NO)];
[[theValue(spiner.isAnimating) should] equal:theValue(YES)];
});
it(@"spiner should be hidden when avatar is loaded", ^
{
IFQuestionCell *cell = (IFQuestionCell *)[tableDelegate tableView:[UITableView new] cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
UIImageView *askerAvatar = (UIImageView *)[cell objectForPropertyName:@"askerAvatar"];
askerAvatar.image = [UIImage new];
UIActivityIndicatorView *spiner = (UIActivityIndicatorView *)[cell objectForPropertyName:@"spiner"];
[spiner shouldNotBeNil];
[[theValue(spiner.hidden) should] equal:theValue(YES)];
});
Как протестировать правильность заполнения таблицы данными?
Для этих целей я обычно использую объект, который поддерживает протоколы «UITableViewDataSource, UITableViewDelegate». Пожалуй, приводить в статье пример тестирования правильности сортировки и уникальности объектов в таблице я не буду, в виду их банальной реализации. Это можно будет посмотреть в примере. Покажу как протестировать подгрузку новых вопросов. Как известно, сервер не может отдавать нам любое количество данных, которое мы у него запросим. Вместо этого, мы получаем данные порциями. В моем случае — это 20 вопросов за раз. То есть я могу отобразить 20 вопросов, а после того, как юзер доскролил до последнего вопроса, я должен буду сделать запрос на сервер для получения следующей порции вопросов. Чтобы пользователь видел, что пошел запрос на сервер за следующей порцией вопросов, я вместо ячейки с вопросами отображаю ячейку со спинерами.
it(@"spiner cell should be last cell, if last cell is not visible on table", ^
{
IFQuestion *q1 = [IFQuestion new];
IFQuestion *q2 = [IFQuestion new];
IFQuestion *q3 = [IFQuestion new];
IFQuestion *q4 = [IFQuestion new];
IFQuestion *q5 = [IFQuestion new];
IFQuestion *q6 = [IFQuestion new];
IFQuestion *q7 = [IFQuestion new];
IFQuestion *q8 = [IFQuestion new];
q1.questionID = 1;
q2.questionID = 2;
q3.questionID = 3;
q4.questionID = 4;
q5.questionID = 5;
q6.questionID = 6;
q7.questionID = 7;
q8.questionID = 8;
[tableDelegate addQuestions:@[q1, q2, q3, q4, q5, q6, q7, q8]];
IFSpinerCell *cell = (IFSpinerCell *)[tableDelegate tableView:tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:9 inSection:0]];
NSString *cellClassName = NSStringFromClass ([cell class]);
[[cellClassName should] equal:NSStringFromClass ([IFSpinerCell class])];
for(NSInteger i = 0; i < 9; i++)
{
cell = (IFSpinerCell *)[tableDelegate tableView:tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
cellClassName = NSStringFromClass ([cell class]);
[[cellClassName should] equal:NSStringFromClass ([IFQuestionCell class])];
}
});
P.S.
Полный пример можно найти, пройдя по этой ссылке. Надеюсь, что статья оказалась полезной. Буду рад ответить на ваши вопросы, а также услышать комментарии и предложения. Спасибо.
Автор: IgorFedorchuk