Xcode 6 ora gestisce i test asincroni con XCTestExpectation
. Quando si verifica un processo asincrono, si stabilisce "l'aspettativa" che questo processo verrà completato in modo asincrono, dopo l'emissione del processo asincrono, si attende quindi che l'aspettativa sia soddisfatta per un periodo di tempo fisso e al termine della query si eseguirà in modo asincrono soddisfare l'aspettativa.
Ad esempio:
- (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];
}
La mia risposta precedente, qui di seguito, è anteriore XCTestExpectation
, ma ho la conserverà per scopi storici.
Perché il test è in esecuzione sulla coda principale, e poiché la richiesta è in esecuzione in modo asincrono, il test non sarà catturare gli eventi nel blocco di completamento. Devi utilizzare un semaforo o un gruppo di distribuzione per rendere la richiesta sincrona.
Ad esempio:
- (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");
}
Il semaforo farà in modo che il test non verrà completata fino a quando la richiesta fa.
Chiaramente, il mio test precedente sta solo facendo una richiesta HTTP casuale, ma si spera che illustri l'idea.E i miei vari XCTAssert
dichiarazioni si identificano quattro tipi di errori:
L'oggetto NSError
non era pari a zero.
Il codice di stato HTTP non era 200.
L'oggetto NSData
era pari a zero.
Il blocco di completamento non è stato completato entro 60 secondi.
Si presumibilmente aggiungono anche test per il contenuto della risposta (cosa che non ho fatto in questo esempio semplificato).
Nota: il test precedente funziona perché non c'è nulla nel mio blocco di completamento che invii qualcosa alla coda principale. Se stai provando questo con un'operazione asincrona che richiede la coda principale (cosa che succederebbe se non stai facendo attenzione ad usare AFNetworking o manualmente alla coda principale tu stesso), puoi ottenere deadlock con lo schema sopra (perché stiamo bloccando il thread principale in attesa che la richiesta di rete finisca). Ma nel caso di NSURLSession
, questo modello funziona alla grande.
Hai chiesto di eseguire test dalla riga di comando, indipendentemente dal simulatore. Ci sono un paio di aspetti:
Se si desidera testare dalla riga di comando, è possibile utilizzare xcodebuild
dalla riga di comando. Ad esempio, per testare su un simulatore dalla riga di comando, sarebbe (nel mio esempio, il mio schema è chiamato NetworkTest
):
xcodebuild test -scheme NetworkTest -destination 'platform=iOS Simulator,name=iPhone Retina (3.5-inch),OS=7.0'
che costruirà il sistema ed eseguirlo sulla destinazione specificata. Nota, ci sono molte segnalazioni di problemi Xcode 5.1 testare le app sul simulatore dalla riga di comando con xcodebuild
(e posso verificare questo comportamento, perché ho una macchina su cui funziona quanto sopra, ma si blocca su un'altra macchina). Il test da riga di comando contro il simulatore in Xcode 5.1 sembra non del tutto affidabile.
Se non si desidera eseguire il test sul simulatore (e questo si applica sia dalla riga di comando o da Xcode), è possibile creare un target MacOS X e disporre di uno schema associato per tale costruire. Ad esempio, ho aggiunto un obiettivo Mac OS X alla mia app, quindi ho aggiunto uno schema chiamato NetworkTestMacOS
per questo.
BTW, se si aggiunge uno schema Mac OS X a un progetto iOS esistente, i test potrebbero non essere aggiunti automaticamente allo schema, quindi potrebbe essere necessario farlo manualmente modificando lo schema, accedere alla sezione dei test, e aggiungi la tua lezione di test lì.È quindi possibile eseguire questi test da Mac OS X. Xcode dalla scelta del sistema di destra, o si può fare dalla riga di comando, anche:
xcodebuild test -scheme NetworkTestMacOS -destination 'platform=OS X,arch=x86_64'
Si noti inoltre, se hai già costruito la vostra destinazione, è possibile eseguire questi test direttamente navigando alla cartella destra DerivedData
(nel mio esempio, è ~/Library/Developer/Xcode/DerivedData/NetworkTest-xxx/Build/Products/Debug
) e quindi eseguire xctest
direttamente dalla linea di comando:
/Applications/Xcode.app/Contents/Developer/usr/bin/xctest -XCTest All NetworkTestMacOSTests.xctest
Un'altra opzione per isolare i test dalla sessione di Xcode è quello di fare il tuo test su un OS X Server separato. Vedere la sezione Continuous Integration and Testing del video WWDC 2013 Testing in Xcode. Entrare in questo punto va ben oltre lo scopo della domanda originale, quindi ti rimanderò semplicemente a quel video che offre una bella introduzione all'argomento.
Personalmente, mi piace molto l'integrazione di test in Xcode (rende la messa a punto di test infinitamente più facile) ed avendo un obiettivo di Mac OS X, si ignora il simulatore in questo processo. Ma se vuoi farlo dalla riga di comando (o OS X Server), forse il sopra aiuta.
Non correlato alla tua domanda originale, quando crei la tua richiesta 'POST', ti suggerisco di evitare la percentuale dei valori (se il nome utente o la password avessero caratteri riservati, come' + 'o' & ', questo non funzionerebbe). Inoltre, è probabilmente buona pratica impostare l'intestazione 'Content-type' su' application/x-www-form-urlencoded'. Ma forse stavi solo cercando di risparmiarci da alcuni di quei dettagli cruenti. :) – Rob