2015-04-08 14 views
31

Sto scrivendo test di integrazione in Xcode 6 per andare a fianco dei miei test unitari e funzionali. XCTest ha un metodo setUp() che viene chiamato prima di ogni test. Grande!Come posso ottenere XCTest per attendere chiamate asincrone in setUp prima dell'esecuzione dei test?

Ha anche XCTestException che consente di scrivere test asincroni. Ottimo anche!

Tuttavia, desidero popolare il mio database di test con i dati di test prima di ogni test e setUp inizia a eseguire i test prima che venga eseguita la chiamata al database asincrono.

C'è un modo per impostare setUp fino a quando il mio database non è pronto prima di eseguire i test?

Ecco un esempio di ciò che ho fatto ora. Dal momento che i rendimenti programma di installazione prima il database è popolando devo duplicare un sacco di codice di prova ogni prova:

func test_checkSomethingExists() { 

    let expectation = expectationWithDescription("") 
    var expected:DatabaseItem 

    // Fill out a database with data. 
    var data = getData() 
    overwriteDatabase(data, { 
     // Database populated. 
     // Do test... in this pseudocode I just check something... 
     db.retrieveDatabaseItem({ expected in 

     XCTAssertNotNil(expected) 

     expectation.fulfill() 
     }) 
    }) 

    waitForExpectationsWithTimeout(5.0) { (error) in 
     if error != nil { 
      XCTFail(error.localizedDescription) 
     } 
    } 

} 

Ecco quello che vorrei:

class MyTestCase: XCTestCase { 

    override func setUp() { 
     super.setUp() 

     // Fill out a database with data. I can make this call do anything, here 
     // it returns a block. 
     var data = getData() 
     db.overwriteDatabase(data, onDone:() ->() { 

      // When database done, do something that causes setUp to end 
      // and start running tests 

     })   
    } 

    func test_checkSomethingExists() { 

     let expectation = expectationWithDescription("") 
     var expected:DatabaseItem 


      // Do test... in this pseudocode I just check something... 
      db.retrieveDatabaseItem({ expected in 

      XCTAssertNotNil(expected) 

      expectation.fulfill() 
     }) 

     waitForExpectationsWithTimeout(5.0) { (error) in 
      if error != nil { 
       XCTFail(error.localizedDescription) 
      } 
     } 

    } 

} 
+0

se si cerca Stack Overflow per "[ios] unit test asincrona" vedrete un sacco di risposte non solo con la (non 'XCTestException') tecnica' XCTestExpectation', ma anche la tecnica del semaforo. per esempio. http://stackoverflow.com/a/23658385/1271826. Probabilmente puoi usare la tecnica del semaforo per il tuo codice di database asincrono (anche se non hai condiviso come stai facendo questo database così non possiamo essere più specifici di quello). Sono sorpreso che la tua libreria di database non abbia una funzione sincrona, perché è molto comune nelle librerie di database. – Rob

+0

Rob, ho modificato la mia domanda per mostrare esattamente quello che sto cercando. So come utilizzare XCTest e XCTestException per scrivere test asincroni. Quello che non so è come mantenere i test in esecuzione fino al setUp. Grazie. –

+0

Lol. Di nuovo, niente di simile a 'XCTestException'. È 'XCTestExpectation'. E come ho detto, usa la tecnica del semaforo in 'setUp', non' XCTestExpectation'. (Usa le aspettative nei test, ma in 'setUp' usa i semafori.) – Rob

risposta

19

Ci sono due tecniche per l'esecuzione di test asincroni. XCTestExpectation e semafori. Nel caso di fare qualcosa di asincrono in setUp, si dovrebbe usare la tecnica del semaforo:

override func setUp() { 
    super.setUp() 

    // Fill out a database with data. I can make this call do anything, here 
    // it returns a block. 

    let data = getData() 

    let semaphore = dispatch_semaphore_create(0) 

    db.overwriteDatabase(data) { 

     // do some stuff 

     dispatch_semaphore_signal(semaphore) 
    } 

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) 
} 

nota, per questo al lavoro, questo blocco onDone non può essere eseguito sul thread principale (altrimenti ti deadlock).


loop Se questo blocco onDone gira sulla coda principale, è possibile utilizzare Esegui:

override func setUp() { 
    super.setUp() 

    var finished = false 

    // Fill out a database with data. I can make this call do anything, here 
    // it returns a block. 

    let data = getData() 

    db.overwriteDatabase(data) { 

     // do some stuff 

     finished = true 
    } 

    while !finished { 
     NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture()) 
    } 
} 

Si tratta di un modello molto inefficiente, ma a seconda di come overwriteDatabase è stata attuata, potrebbe essere necessario

Nota, utilizzare questo modello solo se si è a conoscenza del fatto che il blocco onDone viene eseguito sul thread principale (in caso contrario sarà necessario eseguire la sincronizzazione della variabile finished).

+0

Esiste comunque la possibilità di farlo funzionare se il blocco onDone viene eseguito nel thread principale? –

+0

L'ho aggiornato con un esempio che utilizza runloops se "onDone" viene eseguito sul thread principale. – Rob

63

Invece di utilizzare semafori o loop di blocco, è possibile utilizzare la stessa funzione waitForExpectationsWithTimeout:handler: che si utilizza nei casi di test asincroni.

// Swift 
override func setUp() { 
    super.setUp() 

    let exp = expectation(description: "\(#function)\(#line)") 

    // Issue an async request 
    let data = getData() 
    db.overwriteDatabase(data) { 
     // do some stuff 
     exp.fulfill() 
    } 

    // Wait for the async request to complete 
    waitForExpectations(timeout: 40, handler: nil) 
} 

// Objective-C 
- (void)setUp { 
    [super setUp]; 

    NSString *description = [NSString stringWithFormat:@"%s%d", __FUNCTION__, __LINE__]; 
    XCTestExpectation *exp = [self expectationWithDescription:description]; 

    // Issue an async request 
    NSData *data = [self getData]; 
    [db overwriteDatabaseData: data block: ^(){ 
     [exp fulfill]; 
    }];   

    // Wait for the async request to complete 
    [self waitForExpectationsWithTimeout:40 handler: nil]; 
} 
+2

Grande! Questo è ciò che Apple consiglia: cerca su XCTestExpectation nel riferimento dell'Xcode API. Vorrei poter raddoppiare il voto per includere sia ObjC che Swift! –

+0

Sì, ecco la risposta corretta. –

+2

Questa dovrebbe essere la risposta accettata. C'è un ottimo post di NSHipster che spiega come lavorare con i test asincroni http://nshipster.com/xctestcase/. – isanjosgon

Problemi correlati