Как у меня было первый раз с Kiwi

в 8:58, , рубрики: bdd, objective-c, Песочница, разработка под iOS, метки: , ,
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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js