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. См. раздел Непрерывная интеграция и тестирование видео WWDC 2013 Testing. в Xcode. Вдаваться в это здесь выходит далеко за рамки первоначального вопроса, поэтому я просто отсылаю вас к этому видео, которое дает хорошее введение в тему.
Лично мне очень нравится интеграция тестирования в Xcode (это значительно упрощает отладку тестов), и, имея цель Mac OS X, вы обходите симулятор в этом процессе. Но если вы хотите сделать это из командной строки (или OS X Server), возможно, вышеизложенное поможет.
person
Rob
schedule
14.05.2014
POST
я бы предложил процентное экранирование значений (если имя пользователя или пароль содержат зарезервированные символы, такие как+
или&
, это не сработает). Кроме того, вероятно, хорошей практикой будет установить для заголовкаContent-type
значениеapplication/x-www-form-urlencoded
. Но, возможно, вы просто пытались избавить нас от некоторых кровавых подробностей. :) - person Rob   schedule 14.05.2014