2014-09-25 9 views
7

Próbuję zaktualizować moje asynchroniczne testy jednostkowe, aby użyć nowego interfejsu XCTestExpectation zamiast ręcznie obracać pętlę uruchamiania.Testowanie urządzenia iOS: oczekiwanie na interwał z oczekiwaniami

Moje testy jednostkowe wcześniej wykorzystywane funkcje waitForBlock, finishBlock i waitForTimeInterval: który jest po prostu metoda zwana finishBlock wygoda że po upływie określonego czasu. Próbuję zaktualizować tę konfigurację, aby wykorzystać oczekiwania.

Badania, które zostały z wykorzystaniem waitForBlock + finishBlock semantykę są wszystko działa tak jak oczekiwano po zastąpiony waitForExpectationsWithTime:handler: i fulfill, ale moje rozwiązanie, aby zastąpić waitForTimeInterval: nie wydaje się działać.

- (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:

Wydaje się, że kod faktycznie robi pracy ... więc był to prawdopodobnie tylko Xcode 6 przykręcania ze mną po południu.


czuję się jak powinno być dość prosta: Tworzenie oczekiwania, skonfigurować asynchroniczne blok, który spełnia się i czekać. Jednak blok dispatch_after nigdy nie jest wywoływany.

Moje przeczucie polega na tym, że waitForExpectationsWithTimeout:handler: blokuje bieżący wątek, który jest główną kolejką, więc pętla uruchamiania nigdy nie przechodzi do asynchronicznych bloków. Wydaje się to uzasadnione, ale mam problem z wymyśleniem innego sposobu wdrożenia tej funkcji.

Szukam 1) dodatkowych informacji o XCTestExpectation, które mogą ujawnić obejście, lub 2) innego pomysłu na wdrożenie tej funkcji.

+0

Ciekawe dlaczego robisz to w ten sposób, nie chcesz, aby nie spełnić oczekiwania po pewnym zdefiniowanym opóźnieniem, ale raczej w porządku, gdy zadania są zakończone asynchroniczny? – Mike

+0

Również podany przez Ciebie kod działa dobrze dla mnie ... https://www.dropbox.com/s/lj61uwpbznyruaq/Screenshot%202014-09-252020.43.36.png?dl=0 – Mike

+0

1) Zapewniam, że potrzeba tej funkcjonalności jest trochę zakodowana w kodzie, ale niektóre z naszych zadań w tle są zaporą i zapomnieniem, bez asynchronicznego wywołania zwrotnego. W takich przypadkach upewniamy się, że zadanie w tle kończy się po prostu czekając na mały przedział czasu przed sprawdzeniem stanu wynikowego. – LeffelMania

Odpowiedz

5

Użyłem XCTestExpectation w wielu projektach dla wszelkiego rodzaju sieci asynchronicznej i GCD rzeczy ... AFAIK, coś po to, co wydaje się być najbardziej powszechne użycie:

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

To nie rozwiązuje w pełni postawionego pytania, ale poświęciłeś czas i samo pytanie nie ma zbyt dużej wartości, więc ciesz się z twoich punktów. :-) Twoje zdrowie. – LeffelMania

+0

Pomyślałem, że jest to "inny pomysł na wdrożenie tej funkcjonalności". :) Dzięki – Mike

4

Najwyraźniej za pomocą performSelector:withObject:afterDelay: działa zgodnie z oczekiwaniami, choć nadal nie jestem pewien, dlaczego. Jeśli ktoś ma pomysł, dlaczego GCD dispatch_after nie działa, proszę podać dodatkową odpowiedź, a ja ją zaakceptuję. Na razie ta konfiguracja wydaje się działać zgodnie z oczekiwaniami:

- (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(), ...) nie wydają się działać w Xcode 7 testów UI, przewodnik ukończenie nie jest tzw. performSelector nie jest już dostępna w Swift, ale istnieją dwa inne obejścia:

  1. pomocą zegara

    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. Run blok na globalnej kolejce (to działa, ale prawdopodobnie niebezpieczne, gdyż nie jest udokumentowany wszędzie tam, gdzie XCTestExpectation jest bezpieczny dla wątków).

    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

W jednym z moich testów jednostka musiałem przetestować funkcjonowanie metody w moim głównym kodzie aplikacji, które powinny wywołać czasomierza w około 1 sekundę, aby wywołać inną metodę w aplikacji. Użyłem XCTestExpectation wait i DispatchQueue.asyncAfter jako mechanizmu zatrzymania i oczekiwania przed sprawdzeniem wyniku. Poniższy fragment kodu jest w 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>