Ответ 1
Теперь Xcode 6 обрабатывает асинхронные тесты с помощью XCTestExpectation
. При тестировании асинхронного процесса вы устанавливаете "ожидание", что этот процесс завершится асинхронно, после выдачи асинхронного процесса, вы ждете, пока ожидание будет удовлетворено в течение фиксированного периода времени, и когда запрос завершится, вы будете асинхронно выполнить ожидание.
Например:
- (void)testDataTask
{
XCTestExpectation *expectation = [self expectationWithDescription:@"asynchronous request"];
NSURL *url = [NSURL URLWithString:@"http://www.apple.com"];
NSURLSessionTask *task = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
XCTAssertNil(error, @"dataTaskWithURL error %@", error);
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *) response statusCode];
XCTAssertEqual(statusCode, 200, @"status code was not 200; was %d", statusCode);
}
XCTAssert(data, @"data nil");
// do additional tests on the contents of the `data` object here, if you want
// when all done, Fulfill the expectation
[expectation fulfill];
}];
[task resume];
[self waitForExpectationsWithTimeout:10.0 handler:nil];
}
Мой предыдущий ответ ниже, предшествует XCTestExpectation
, но я сохраню его в исторических целях.
Поскольку ваш тест выполняется в главной очереди, и поскольку ваш запрос выполняется асинхронно, ваш тест не будет захватывать события в блоке завершения. Вы должны использовать семафор или группу отправки, чтобы сделать запрос синхронным.
Например:
- (void)testDataTask
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURL *url = [NSURL URLWithString:@"http://www.apple.com"];
NSURLSessionTask *task = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
XCTAssertNil(error, @"dataTaskWithURL error %@", error);
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = [(NSHTTPURLResponse *) response statusCode];
XCTAssertEqual(statusCode, 200, @"status code was not 200; was %d", statusCode);
}
XCTAssert(data, @"data nil");
// do additional tests on the contents of the `data` object here, if you want
// when all done, signal the semaphore
dispatch_semaphore_signal(semaphore);
}];
[task resume];
long rc = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 60.0 * NSEC_PER_SEC));
XCTAssertEqual(rc, 0, @"network request timed out");
}
Семафор гарантирует, что тест не будет завершен до тех пор, пока запрос не выполнит.
Ясно, что мой вышеприведенный тест просто делает случайный HTTP-запрос, но, надеюсь, он иллюстрирует идею. И мои различные операторы XCTAssert
идентифицируют четыре типа ошибок:
-
Объект
NSError
не был нулевым. -
Код статуса HTTP не был 200.
-
Объект
NSData
был равен нулю. -
Блок завершения не завершился в течение 60 секунд.
Предположительно вы также добавите тесты для содержимого ответа (чего я не делал в этом упрощенном примере).
Обратите внимание, что вышеупомянутый тест работает, потому что в моем блоке завершения ничего не происходит, что отправляет что-либо в основную очередь. Если вы тестируете это с помощью асинхронной операции, для которой требуется основная очередь (что произойдет, если вы не будете осторожны при использовании AFNetworking или вручную отправляете в основную очередь самостоятельно), вы можете получить взаимоблокировки с указанным выше шаблоном (поскольку мы блокируем основной поток ожидает завершения запроса сети). Но в случае NSURLSession
этот шаблон отлично работает.
Вы спросили о проведении тестирования из командной строки, независимо от симулятора. Существует несколько аспектов:
-
Если вы хотите проверить из командной строки, вы можете использовать
xcodebuild
из командной строки. Например, чтобы проверить на симуляторе из командной строки, это будет (в моем примере моя схема называетсяNetworkTest
):xcodebuild test -scheme NetworkTest -destination 'platform=iOS Simulator,name=iPhone Retina (3.5-inch),OS=7.0'
Это построит схему и запустит ее в указанном месте назначения. Обратите внимание: есть много сообщений о проблемах Xcode 5.1, которые тестируют приложения на симуляторе из командной строки с помощью
xcodebuild
(и я могу проверить это поведение, потому что у меня есть одна машина, на которой это работает отлично, но она зависает на другом). Тест командной строки против симулятора в Xcode 5.1 выглядит не совсем надежным. -
Если вы не хотите, чтобы ваш тест запускался на симуляторе (и это относится к тому, выполняются ли они из командной строки или из Xcode), вы можете создать целевую систему MacOS X и иметь соответствующую схему для которые строят. Например, я добавил целевое приложение Mac OS X для своего приложения, а затем добавил для него схему под названием
NetworkTestMacOS
.Кстати, если вы добавите схему Mac OS X и существующий проект iOS, тесты могут не добавляться автоматически к схеме, поэтому вам, возможно, придется это сделать вручную, отредактировав схему, перейдите в раздел "Тесты" и добавьте свой тестовый класс. Затем вы можете запустить эти тесты Mac OS X из Xcode, выбрав правильную схему или вы также можете сделать это из командной строки:
xcodebuild test -scheme NetworkTestMacOS -destination 'platform=OS X,arch=x86_64'
Также обратите внимание: если вы уже создали свою цель, вы можете запустить эти тесты напрямую, перейдя в правую папку
DerivedData
(в моем примере, она~/Library/Developer/Xcode/DerivedData/NetworkTest-xxx/Build/Products/Debug
), а затем запустивxctest
непосредственно из команды строка:/Applications/Xcode.app/Contents/Developer/usr/bin/xctest -XCTest All NetworkTestMacOSTests.xctest
-
Еще один вариант для изоляции ваших тестов с вашего сеанса Xcode - это выполнить тестирование на отдельном OS X Server. См. Раздел "Непрерывная интеграция и тестирование" видеоролика WWDC 2013 Тестирование в Xcode. Чтобы войти в то, что здесь выходит далеко за рамки исходного вопроса, поэтому я просто отправлю вас к тому видео, которое дает хорошее введение в эту тему.
Лично мне очень нравится интеграция тестирования в Xcode (это делает отладку тестов бесконечно проще), и имея цель Mac OS X, вы обходите симулятор в этом процессе. Но если вы хотите сделать это из командной строки (или OS X Server), возможно, это поможет.