Тестирование XCTest для вызываемых методов делегатов

Я пытаюсь проверить некоторые классы, которые я создал, которые выполняют сетевые действия, используя класс NSNetServer, среди прочих. У меня возникли некоторые проблемы, гарантирующие, что вызывается метод делегата.

Я пробовал множество методов, в том числе:

Используя [NSThread sleepForTimeInterval:5.0f]; и [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0f]];, просто остановитесь, пока происходят другие действия. Метод NSRunLoop работает в первый раз, когда он вызывается (как показано в примере кода ниже), но сбой во втором вызове. Я не понимаю, что это "правильный" способ делать вещи, но я не знаю, что такое "правильный".

Использование классов NSCondition и NSConditionLock, которые просто блокируют код, и обратный вызов никогда не вызывается.

Использование цикла while для переменной, измененной в методе обратного вызова, так же, как указано выше.

Ниже приведен код с несколькими дополнительными комментариями, а некоторые из тестов удалены для простоты:

- (void)testCheckCredentials
{
    [self.server start];
    // Create a client
    self.nsb = [[NSNetServiceBrowser alloc] init];
    // Set the delegate to self
    self.nsb.delegate = self;
    // Search for the server
    [self.nsb searchForServicesOfType:self.protocol inDomain:@""];
    // Wait for the service to be found and resolved
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:self.timeout]];
    XCTAssertTrue(self.serviceWasFound, @"Service was not found");
    // Open the connection to the server
    XCTAssertTrue([self.serverConnection open], @"Connection to server failed to open");
    // Wait for the client to connect
    /* This is where it crashes */
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:self.timeout]];
    XCTAssertTrue(self.clientDidConnect, @"Client did not connect");
    /* Further, more class-specific tests */
}

- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing
{
    NSLog(@"Found a service: %@ (%@)", service.name, service.domain);
    if ([self.serverName isEqualToString:service.name]) {
        self.serviceWasFound = YES;
    }
}

- (void)clientDidConnect:(RCFClientConnection *)client
{
    XCTAssertNotNil(client, @"Connected client is nil");
    self.clientConnection = client;
    self.clientDidConnect = YES;
}

Я также попытался сделать lock в объекте NSCondition:

[self.nsb searchForServicesOfType:self.protocol inDomain:@""];
// Wait for the service to be found and resolved
[self.lock lockWhenCondition:1];
XCTAssertTrue(self.serviceWasFound, @"Service was not found");

и

self.serviceWasFound = YES;
[self.lock unlockWithCondition:1]

При использовании метода блокировки метод netServiceBrowser:didFindService:moreComing: никогда не вызывается, тот же, когда я использую:

while (!self.serviceWasFound) {};

Я все еще участвую Objective-C, но я просто полностью застрял в этой проблеме.

Ответ 1

Чтобы обрабатывать компоненты тестирования, которые вызывают асинхронное выполнение методов и функций, XCTest был расширен в Xcode 6, чтобы включить возможность обработки блоков с использованием нового API и объектов класса XCTestExpectation. Эти объекты реагируют на новые методы XCTest, которые позволяют тестовому методу ждать, пока не вернется вызов асинхронного вызова или тайм-аут.

Вот ссылка на документацию для яблока для вышеупомянутого экстракт. Написание тестов асинхронных операций

@interface sampleAPITests : XCTestCase<APIRequestClassDelegate>{
APIRequestClass *apiRequester;
XCTestExpectation *serverRespondExpectation;
}
@end

//implementation test class
- (void)testAPIConnectivity {
// This is an example of a functional test case.
serverRespondExpectation = [self expectationWithDescription:@"server responded"];
[apiRequester sendAPIRequestForMethod:nil withParams:nil];//send request to server to get tap info
apiRequester.delegate = self;
[self waitForExpectationsWithTimeout:1 handler:^(NSError *error) {
    if (error) {
        NSLog(@"Server Timeout Error: %@", error);
    }
   nslog(@"execute here after delegate called  or timeout");
}];
XCTAssert(YES, @"Pass");
}

//Delegate implementation
- (void) request:(APIRequest *)request didReceiveResponse:(NSDictionary *)jsonResponse success:(BOOL)success{
[serverRespondExpectation fulfill];
XCTAssertNotNil(jsonResponse,@"json object returned from server is nil");
}

Ответ 2

Несколько тестовых библиотек поддерживают тестирование асинхронных и поточных операций.

Все они работают по-разному:

  • Подождите x секунд для возникновения условия.
  • Необязательно выполнить набор утверждений после первого условия, если не выполнено одно из них.
  • Не удалось выполнить требуемое условие в течение указанного (или по умолчанию) времени.

Вот несколько библиотек, которые предоставляют следующие возможности:

  • expecta соответствующая библиотека.
  • Структура Kiwi.
  • Typhoon В инфраструктуре DI есть утилита для проведения асинхронных интеграционных тестов.

Насколько я знаю, все эти библиотеки используют подход с циклическим циклом, поскольку другие могут привести к взаимоблокировкам. Для того чтобы этот подход работал надежно:

  • При тестировании обратных вызовов на основе блоков вы можете создать встроенный блок.
  • Для обратных вызовов делегатов вы должны создать отдельный заглушку (простейшая возможная реализация протокола) или использовать макет ( "магическая" реализация, созданная с помощью насмешливой библиотеки) и установить это как делегат. У вас могут возникнуть проблемы, если вы установите экземпляр теста как делегата.

Edit:

По состоянию на Xcode6 существует новое утверждение XCTestExpectation для асинхронного тестирования.

Каждая из вышеуказанных библиотек может быть установлена ​​с помощью CocoaPods.