2015-04-08 8 views
31

Ich schreibe Integrationstests in Xcode 6, um sie mit meinen Unit- und Funktionstests zu kombinieren. XCTest hat eine setUp() -Methode, die vor jedem Test aufgerufen wird. Groß!Wie kann ich XCTest auf Async-Aufrufe in Setup warten, bevor Tests ausgeführt werden?

Es hat auch XCTestException, die mich Async-Tests schreiben lassen. Auch toll!

Allerdings möchte ich meine Testdatenbank vor jedem Test mit Testdaten füllen und setUp startet gerade Tests, bevor der asynchrone Datenbankaufruf durchgeführt wird.

Gibt es eine Möglichkeit zu warten, bis meine Datenbank bereit ist, bevor es Tests ausführt?

Hier ist ein Beispiel von dem, was ich jetzt mache. Da setUp kehrt vor der Datenbank gemacht werden bevölkert Ich habe eine Menge von Test-Code kopieren jeden Test:

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) 
     } 
    } 

} 

Hier ist, was würde Ich mag:

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

Wenn Sie Überlauf Suche Stack für „[ios] asynchronen Unit-Test“ Sie werden eine Menge Antworten sehen nicht nur mit der 'XCTestExpectation' (nicht' XCTestException') Technik, sondern auch die Semaphore Technik. z.B. http://StackOverflow.com/a/23658385/1271826. Sie können wahrscheinlich die Semaphor-Technik für Ihren asynchronen Datenbankcode verwenden (obwohl Sie nicht angegeben haben, wie Sie diesen Datenbankkram machen, so dass wir nicht spezifischer sein können). Ich bin überrascht, dass Ihre Datenbankbibliothek kein synchrones Feature hat, weil das in Datenbankbibliotheken sehr häufig ist. – Rob

+0

Rob, ich habe meine Frage bearbeitet, um genau zu zeigen, wonach ich suche. Ich weiß, wie man XCTest und XCTestException benutzt, um async Tests zu schreiben. Was ich nicht weiß, ist, wie man die Tests so lange laufen lässt, bis setUp fertig ist. Vielen Dank. –

+0

Lol. Auch hier gibt es keine "XCTestException". Es ist 'XCTestExpectation'. Und wie gesagt, benutze Semaphortechnik in 'setUp', nicht' XCTestExpectation'. (Verwenden Sie Erwartungen in den Tests, aber in 'setUp' Semaphore verwenden.) – Rob

Antwort

19

Es gibt zwei Techniken für asynchrone Tests ausgeführt werden. XCTestExpectation und Semaphoren. Im Fall von etwas asynchron in setUp tun, sollten Sie die Semaphore Technik verwenden:

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) 
} 

Hinweis, dafür zu arbeiten, dieser onDone Block auf dem Haupt-Thread nicht ausgeführt werden kann (oder sonst werden Sie Deadlock).


Wenn dieser onDone Block auf der Hauptwarteschlange ausgeführt wird, können Sie laufen Loops verwenden:

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()) 
    } 
} 

Dies ist eine sehr ineffiziente Muster, aber je nachdem, wie overwriteDatabase implementiert wurde, kann es notwendig sein

Hinweis: Verwenden Sie dieses Muster nur, wenn Sie wissen, dass der Block auf dem Hauptthread ausgeführt wird (andernfalls müssen Sie eine Synchronisierung der Variablen finished durchführen).

+0

Gibt es trotzdem, dass das funktioniert, wenn der onDone Block im Hauptthread läuft? –

+0

Ich habe dies mit einem Beispiel aktualisiert, das Runloops verwendet, wenn 'onDone' auf dem Hauptthread ausgeführt wird. – Rob

63

Anstatt Semaphoren oder Blockierungsschleifen zu verwenden, können Sie dieselbe waitForExpectationsWithTimeout:handler:-Funktion verwenden, die Sie in Ihren asynchronen Testfällen verwenden.

// 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

Großartig! Dies empfiehlt Apple - suchen Sie in der Xcode-API-Referenz nach XCTestExpectation. Ich wünschte, ich könnte für die Aufnahme von ObjC und Swift stimmen! –

+0

Ja, hier ist die richtige Antwort. –

+2

Dies sollte die akzeptierte Antwort sein. Es gibt einen tollen Beitrag von NSHipster, der erklärt, wie man mit asynchronem Testen arbeitet http://shipster.com/xctestcase/. – isanjosgon