2014-09-25 6 views
7

Ich versuche, meine asynchronen Unit-Tests zu aktualisieren, um die neue XCTestExpectation Schnittstelle zu verwenden, anstatt die Laufschleife manuell zu drehen.iOS Unit Testing: Warten auf Zeitintervall mit Erwartungen

zuvor My Unit-Tests die Funktionen genutzt waitForBlock, finishBlock und waitForTimeInterval: die einfach eine bequeme Methode ist, die finishBlock nach der angegebenen Zeit aufgerufen. Ich versuche, dieses Setup zu aktualisieren, um die Erwartungen zu nutzen.

Die Tests, die waitForBlock + finishBlock Semantik wurden unter Verwendung arbeiten alle nur nach wie erwartet mit waitForExpectationsWithTime:handler: und fulfill ersetzt werden, aber meine Lösung waitForTimeInterval: ersetzen nicht zu funktionieren scheinen.

- (void)waitForTimeInterval:(NSTimeInterval)delay 
{ 
    XCTestExpectation *expectation = [self expectationWithDescription:@"wait"]; 
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 
     [expectation fulfill]; 
    }); 

    [self waitForExpectationsWithTimeout:delay + 1 handler:nil]; 
} 

Edit:

Scheint, wie dieser Code tatsächlich tut Arbeit ... so war dies wahrscheinlich nur Xcode 6 mit mir an diesem Nachmittag zu schrauben.


Ich fühle mich wie es recht sein sollte geradlinig: Eine Erwartung, einen asynchronen Block, der eingerichtet erfüllt ist, und warten. Der dispatch_after-Block wird jedoch nie aufgerufen.

Meine Ahnung ist, dass waitForExpectationsWithTimeout:handler: blockiert seinen aktuellen Thread, der die Hauptwarteschlange ist, so dass die RUN-Schleife nie um seine asynchronen Blöcke herumkommt. Das scheint vernünftig, aber ich habe Schwierigkeiten, eine andere Möglichkeit zu finden, diese Funktionalität zu implementieren.

Ich suche entweder 1) zusätzliche Informationen über XCTestExpectation, die eine Problemumgehung aufdecken könnte, oder 2) eine andere Idee für die Implementierung dieser Funktionalität.

+0

Neugierig, warum Sie es auf diese Weise tun, würden Sie nicht wollen, nicht die Erwartungen nach einiger vordefinierten Verzögerung erfüllen, sondern recht, wenn Ihre async Aufgaben abgeschlossen sind? – Mike

+0

Auch der Code, den Sie zur Verfügung gestellt scheint scheint gut für mich ... https://www.dropbox.com/s/lj61uwpbznyruaq/Screenshot%202014-09-25%2022.43.36.png?dl=0 – Mike

+0

1) Ich gebe Ihnen zu, dass die Notwendigkeit für diese Funktionalität ein bisschen ein Code-Geruch ist, aber einige unserer Hintergrundaufgaben sind Feuer-und-vergessen, ohne einen asynchronen Rückruf. In diesen Fällen stellen wir sicher, dass die Hintergrundaufgabe abgeschlossen wird, indem nur auf ein kleines Zeitintervall gewartet wird, bevor der resultierende Zustand verifiziert wird. – LeffelMania

Antwort

5

I XCTestExpectation in einer Reihe von Projekten für alle Arten von Asynchron-Bahn und GCD stuff ... AFAIK, so etwas wie die folgenden verwendet haben ist, was scheint die häufigste Verwendung sein:

- (void)testExample { 
    // create the expectation with a nice descriptive message 
    XCTestExpectation *expectation = [self expectationWithDescription:@"The request should successfully complete within the specific timeframe."]; 

    // do something asyncronously 
    [someObject doAsyncWithCompletionHandler:^(NSInteger yourReturnValue) { 
     // now that the async task it done, test whatever states/values you expect to be after this is 
     // completed 
     NSInteger expectedValue = 42; 
     XCTAssert(yourReturnValue == expectedValue, @"The returned value doesn't match the expected value."); 

     // fulfill the expectation so the tests can complete 
     [expectation fulfill]; 
    }]; 

    // wait for the expectations to be called and timeout after some 
    // predefined max time limit, failing the test automatically 
    NSTimeInterval somePredefinedTimeout = 3; 
    [self waitForExpectationsWithTimeout:somePredefinedTimeout handler:nil]; 
} 
+0

Dies beantwortet die gestellte Frage nicht ganz, aber Sie haben sich die Zeit genommen und die Frage selbst ist nicht wirklich von großem Wert, also genießen Sie Ihre Punkte. :-) Prost. – LeffelMania

+0

Ich dachte, es fiel unter "eine andere Idee für die Implementierung dieser Funktionalität." :) Vielen Dank – Mike

4

Scheinbar mit performSelector:withObject:afterDelay: funktioniert wie erwartet, obwohl ich immer noch nicht sicher bin warum. Wenn jemand eine Idee hat, warum GCD's dispatch_after nicht funktioniert, bitte geben Sie eine zusätzliche Antwort und ich werde es akzeptieren. Vorerst scheint dieses Setup zu arbeiten, wie erwartet:

- (void)waitForTimeInterval:(NSTimeInterval)delay 
{ 
    XCTestExpectation *expectation = [self expectationWithDescription:@"wait"]; 
    [self performSelector:@selector(fulfillExpectation:) withObject:expectation afterDelay:delay]; 

    [self waitForExpectationsWithTimeout:delay + 1 handler:nil]; 
} 

- (void)fulfillExpectation:(XCTestExpectation *)expectation 
{ 
    [expectation fulfill]; 
} 
8

dispatch_async(dispatch_get_main_queue(), ...) nicht in Xcode 7 UI Tests scheinen zu funktionieren, wird die Abschluss-Handler nie aufgerufen. performSelector ist nicht mehr in Swift, aber es gibt zwei andere Lösungen:

  1. einen Timer

    var waitExpectation: XCTestExpectation? 
    
    func wait(duration: NSTimeInterval) { 
        waitExpectation = expectationWithDescription("wait") 
        NSTimer.scheduledTimerWithTimeInterval(duration, target: self, 
         selector: Selector("onTimer"), userInfo: nil, repeats: false) 
        waitForExpectationsWithTimeout(duration + 3, handler: nil) 
    } 
    
    func onTimer() { 
        waitExpectation?.fulfill() 
    } 
    
  2. Führen Sie den Block auf die globale Warteschlange verwenden (es funktioniert, aber wahrscheinlich unsicher, da es nicht dokumentiert ist überall, wo XCTestExpectation Thread-sicher ist).

    func wait(duration: NSTimeInterval) { 
        let expectation = expectationWithDescription("wait") 
        let dispatchTime = dispatch_time(DISPATCH_TIME_NOW, 
         Int64(duration * Double(NSEC_PER_SEC))) 
        dispatch_after(dispatchTime, 
         dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { 
         expectation.fulfill() 
        } 
        waitForExpectationsWithTimeout(duration + 3, handler: nil) 
    } 
    
0

In einem meiner Einheit Testfälle ich brauchte den Lauf eines Verfahrens in meinem Haupt-App-Code zu testen, die einen Zeitgeber in etwa 1 Sekunde auslösen sollte eine andere Methode in der App aufrufen. Ich habe XCTestExpectation warten und DispatchQueue.asyncAfter als einen Mechanismus zu stoppen und warten, bevor ich das Ergebnis überprüfen. Der folgende Code ist ein Ausschnitt in Swift 3:

<call the main app method which will trigger a timer event> 

    // wait 
    let expectation = XCTestExpectation(description: "test") 
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) { 
     expectation.fulfill() 
    } 
    wait(for: [expectation], timeout: 2.5) 

    <check the result of the timer event>