Как выполнить модульное тестирование делегата NSURLConnection? - PullRequest
11 голосов
/ 28 марта 2012

Как я могу провести модульное тестирование моего делегата NSURLConnection?Я создал класс ConnectionDelegate, который соответствует различным протоколам для передачи данных из Интернета в разные ViewControllers.Прежде чем я зайду слишком далеко, я хочу начать писать свои модульные тесты.Но я не знаю, как проверить их как единое целое без подключения к интернету.Я хотел бы также, что я должен сделать, чтобы рассматривать асинхронные обратные вызовы.

Ответы [ 6 ]

15 голосов
/ 29 марта 2012

Это похоже на ответ Джона, хотя и не помещается в комментарии.Первый шаг - убедиться, что вы не создаете реальное соединение.Самый простой способ добиться этого - перетащить создание соединения в фабричный метод, а затем заменить фабричный метод в своем тесте.С частичной поддержкой OCMock это может выглядеть следующим образом.

В вашем реальном классе:

- (NSURLConnection *)newAsynchronousRequest:(NSURLRequest *)request
{
    return [[NSURLConnection alloc] initWithRequest:request delegate:self];
}

В вашем тесте:

id objectUnderTest = /* create your object */
id partialMock = [OCMockObject partialMockForObject:objectUnderTest];
NSURLConnection *dummyUrlConnection = [[NSURLConnection alloc] 
    initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"file:foo"]] 
    delegate:nil startImmediately:NO];
[[[partialMock stub] andReturn:dummyUrlConnection] newAsynchronousRequest:[OCMArg any]];

Теперь, когда тестируемый объект пытается создать URL-соединение, он фактически получаетфиктивное соединение создано в тесте.Фиктивное соединение не обязательно должно быть действительным, потому что мы его не запускаем и оно никогда не используется.Если ваш код использует соединение, вы можете вернуть другой макет, тот, который имитирует NSURLConnection.

Второй шаг - вызвать метод для вашего объекта, который инициирует создание NSURLConnection:

[objectUnderTest doRequest];

Поскольку тестируемый объект не использует реальное соединение, теперь мы можем вызывать методы делегата из теста.Для NSURLResponse мы используем другой макет, данные ответа создаются из строки, определенной в другом месте теста:

int statusCode = 200;
id responseMock = [OCMockObject mockForClass:[NSHTTPURLResponse class]];
[[[responseMock stub] andReturnValue:OCMOCK_VALUE(statusCode)] statusCode];
[objectUnderTest connection:dummyUrlConnection didReceiveResponse:responseMock];

NSData *responseData = [RESPONSE_TEXT dataUsingEncoding:NSASCIIStringEncoding];
[objectUnderTest connection:dummyUrlConnection didReceiveData:responseData];

[objectUnderTest connectionDidFinishLoading:dummyUrlConnection];

Вот и все.Вы фактически фальсифицировали все взаимодействия, которые тестируемый объект имеет с соединением, и теперь вы можете проверить, находится ли он в том состоянии, в котором он должен быть.

Если вы хотите увидеть некоторый «настоящий» код,взгляните на тесты для класса из проекта CCMenu, который использует NSURLConnections.Это немного сбивает с толку, потому что тестируемый класс тоже называется соединением.

http://ccmenu.svn.sourceforge.net/viewvc/ccmenu/trunk/CCMenuTests/Classes/CCMConnectionTest.m?revision=129&view=markup

9 голосов
/ 08 ноября 2012

РЕДАКТИРОВАТЬ (2-18-2014): я только что наткнулся на эту статью с более элегантным решением.

http://www.infinite -loop.dk / blog / 2011/04 / unittesting-asynchronous-network-access /

По сути, у вас есть следующий метод:

- (BOOL)waitForCompletion:(NSTimeInterval)timeoutSecs {
    NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs];

    do {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
        if([timeoutDate timeIntervalSinceNow] < 0.0)
            break;
    } while (!done);

    return done;
}

В конце вашего метода тестирования вы убедитесь, что время не истекло:

STAssertTrue([self waitForCompletion:5.0], @"Timeout");

Базовый формат:

- (void)testAsync
{
    // 1. Call method which executes something asynchronously 
    [obj doAsyncOnSuccess:^(id result) {
        STAssertNotNil(result);
        done = YES;
    }
    onError:^(NSError *error) [
        STFail();
        done = YES;
    }

    // 2. Determine timeout
    STAssertTrue([self waitForCompletion:5.0], @"Timeout");
}    

==============

Я опаздываю на вечеринку, но янаткнулся на очень простое решение.(Большое спасибо за http://www.cocoabuilder.com/archive/xcode/247124-asynchronous-unit-testing.html)

.h файл:

@property (nonatomic) BOOL isDone;

.m файл:

- (void)testAsynchronousMethod
{
    // 1. call method which executes something asynchronously.

    // 2. let the run loop do its thing and wait until self.isDone == YES
    self.isDone = NO;
    NSDate *untilDate;
    while (!self.isDone)
    {
        untilDate = [NSDate dateWithTimeIntervalSinceNow:1.0]
        [[NSRunLoop currentRunLoop] runUntilDate:untilDate];
        NSLog(@"Polling...");
    }

    // 3. test what you want to test
}

isDone установлен на YES впоток, который выполняет асинхронный метод.

Итак, в этом случае я создал и запустил NSURLConnection на шаге 1 и сделал его делегатом этот тестовый класс. В

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response

Я установил self.isDone = YES;. Мы вырываемся из цикла while и тест выполняется. Готово.

5 голосов
/ 29 марта 2012

Мой любимый способ сделать это - создать подкласс NSURLProtocol и заставить его отвечать на все запросы http - или другие протоколы в этом отношении. Затем вы регистрируете протокол тестирования в своем методе -setup и отменяете его регистрацию в своем методе -tearDown. Затем вы можете заставить этот протокол тестирования передавать некоторые хорошо известные данные обратно в ваш код, чтобы вы могли проверить их в своих модульных тестах.

Я написал несколько статей в блоге на эту тему. Наиболее подходящим для вашей проблемы может быть Использование NSURLProtocol для ввода тестовых данных и Модульное тестирование асинхронного доступа к сети .

Вы также можете взглянуть на мой протокол ILCannedURLProtocol, который описан в предыдущих статьях. Источник доступен по адресу Github .

5 голосов
/ 29 марта 2012

Я избегаю сетей в модульных тестах. Вместо этого:

  • Я выделяю NSURLConnection в методе.
  • Я создаю подкласс тестирования, переопределяя этот метод для удаления всех следов NSURLConnection.
  • Я пишу один тест, чтобы гарантировать, что рассматриваемый метод будет вызван, когда я захочу. Тогда я знаю, что в реальной жизни сработает NSURLConnection.

Затем я сконцентрируюсь на более интересной части: синтезирующий фиктивный NSURL отвечает с различными характеристиками и передаю их методам NSURLConnectionDelegate.

0 голосов
/ 27 июня 2013

вы можете дать этому шанс:

https://github.com/marianoabdala/ZRYAsyncTestCase

Вот пример кода NSURLConnection, который тестируется модулем: https://github.com/marianoabdala/ZRYAsyncTestCase/blob/12a84c7f1af1a861f76c7825aef6d9d6c53fd1ca/SampleProject/SampleProjectTests/SampleProjectTests.m#L33-L56

0 голосов
/ 28 марта 2012

Вот что я делаю:

  1. Получить панель управления XAMPP http://www.apachefriends.org/en/xampp.html
  2. Запустите сервер Apache
  3. В папке ~/Sites поместите тестовый файл (любые данные, которые вы хотите, мы назовем my file.test).
  4. Начните делегировать, используя URL http://localhost/~username/myfile.test
  5. Остановите сервер Apache, когда он не используется.
...