Xcode UI 테스트의 테스트 케이스에서 지연 / 대기


182

Xcode 7 베타 2에서 제공되는 새로운 UI Testing을 사용하여 테스트 사례를 작성하려고합니다. 앱에 로그인하기 위해 서버를 호출하는 로그인 화면이 있습니다. 비동기 작업이므로 이와 관련된 지연이 있습니다.

추가 단계를 진행하기 전에 XCTestCase에서 지연 또는 대기 메커니즘을 유발할 수있는 방법이 있습니까?

사용 가능한 적절한 문서가 없으며 클래스의 헤더 파일을 살펴 보았습니다. 이와 관련된 것을 찾을 수 없었습니다.

어떤 아이디어 / 제안?


13
나는 NSThread.sleepForTimeInterval(1)작동해야 한다고 생각 한다
Kametrixom

큰! 작동하는 것 같습니다. 그러나 그것이 권장되는 방법인지 확실하지 않습니다. 애플이 더 나은 방법을 제시해야한다고 생각합니다.
Tejas HS

실제로 실제로는 괜찮다고 생각합니다. 실제로 특정 시간 동안 현재 스레드를 일시 중지하는 가장 일반적인 방법입니다. 더 많은 제어를 원한다면 GCD (The dispatch_after, dispatch_queuestuff) 로 들어갈 수도 있습니다
Kametrixom

@Kametrixom 실행 루프를 체크하지 마십시오-Apple은 베타 4에서 기본 비동기 테스트를 도입했습니다. 자세한 내용은 답변 을 참조하십시오.
Joe Masilotti

2
Swift 4.0-> Thread.sleep (forTimeInterval : 2)
uplearnedu.com

답변:


168

비동기 UI 테스트는 Xcode 7 Beta 4에서 도입되었습니다. "Hello, world!"라는 텍스트가있는 레이블을 기다리는 것입니다. 다음과 같이 할 수 있습니다.

let app = XCUIApplication()
app.launch()

let label = app.staticTexts["Hello, world!"]
let exists = NSPredicate(format: "exists == 1")

expectationForPredicate(exists, evaluatedWithObject: label, handler: nil)
waitForExpectationsWithTimeout(5, handler: nil)

UI 테스팅에 대한 자세한 내용은 내 블로그에서 확인할 수 있습니다.


19
불행히도 시간 초과가 발생했다는 사실을 받아 들일 방법이 없습니다 waitForExpectationsWithTimeout. 불행히도 테스트가 자동으로 실패합니다.
Jedidja

@Jedidja 실제로, 이것은 XCode 7.0.1에서는 발생하지 않습니다.
Bastian

@Bastian 흠 흥미로운; 다시 확인해야합니다.
Jedidja

1
그것은 나를 위해 작동하지 않습니다. 내 샘플은 다음과 같습니다. let xButton = app.toolbars.buttons [ "X"] let exist = NSPredicate (format : "exists == 1") expectationForPredicate (exists, evaluationWithObject : xButton, handler : nil) waitForExpectationsWithTimeout (10, handler : nil)
emoleumassi

app.launch()단지 응용 프로그램을 다시 시작하는 것 같다. 그게 필요 할까?
Chris Prince

225

또한 잠을 잘 수 있습니다.

sleep(10)

UITest는 다른 프로세스에서 실행되므로 작동합니다. 그것이 얼마나 바람직한 지 모르겠지만 작동합니다.


2
언젠가 우리는 그것을 지연시키고 실패를 일으키고 싶지 않은 방법이 필요합니다! 감사
Tai Le

13
내가 본 것 중 가장 좋은 답변 :) + 100 표를 추가하겠습니다. 가능하다면 :
Bartłomiej Semańczyk

8
서브 초 지연을 지정할 수 있으므로 NSThread.sleepForTimeInterval (0.2)이 좋습니다. (sleep ()은 정수 매개 변수를 사용하며 1 초의 배수 만 가능합니다).
Graham Perks 2019

5
@GrahamPerks, 그렇습니다.usleep
mxcl

3
그것은 좋지 않은 제안 (UITesting이 작동하는 방식을 얻지 못함)은 아니지만 그것이 나쁜 제안 이었더라도 때로는 작동하는 기대를 만들 수있는 방법이 없습니다 (시스템은 누구에게 경고합니까?) 그래서 이것이 당신이 가진 전부입니다.
mxcl

78

iOS 11 / Xcode 9

<#yourElement#>.waitForExistence(timeout: 5)

이것은이 사이트의 모든 커스텀 구현을 대체합니다!

https://stackoverflow.com/a/48937714/971329 에서 내 대답을 확인하십시오 . 테스트를 실행하는 시간을 크게 단축시키는 요청을 기다리는 대안을 설명합니다!


감사합니다 @daidai 텍스트를 변경했습니다 :)
blackjacx

1
그러나 이것은 여전히 ​​사용할 때 접근하는 방법 XCTestCase이며 매력처럼 작동합니다. sleep(3)테스트 시간이 인위적으로 연장되고 테스트 스위트가 커질 때 실제로 선택 사항이 아니기 때문에 이와 같은 접근 방식이 왜 그렇게 많이 투표 되는지 이해하지 못합니다 .
blackjacx

실제로 Xcode 9가 필요하지만 iOS 10을 실행하는 장치 / 시뮬레이터에서도 작동합니다. ;-)
d4Rk

예, 위의 제목에 썼습니다. 하지만 지금 대부분의 사람들은 적어도 엑스 코드 9 ;-)로 업그레이드해야
blackjacx

77

Xcode 9XCTWaiter에 새로운 트릭을 도입 했습니다

테스트 케이스가 명시 적으로 대기

wait(for: [documentExpectation], timeout: 10)

웨이터 인스턴스 위임 테스트

XCTWaiter(delegate: self).wait(for: [documentExpectation], timeout: 10)

웨이터 클래스는 결과를 반환

let result = XCTWaiter.wait(for: [documentExpectation], timeout: 10)
switch(result) {
case .completed:
    //all expectations were fulfilled before timeout!
case .timedOut:
    //timed out before all of its expectations were fulfilled
case .incorrectOrder:
    //expectations were not fulfilled in the required order
case .invertedFulfillment:
    //an inverted expectation was fulfilled
case .interrupted:
    //waiter was interrupted before completed or timedOut
}

샘플 사용법

Xcode 9 이전

목표 C

- (void)waitForElementToAppear:(XCUIElement *)element withTimeout:(NSTimeInterval)timeout
{
    NSUInteger line = __LINE__;
    NSString *file = [NSString stringWithUTF8String:__FILE__];
    NSPredicate *existsPredicate = [NSPredicate predicateWithFormat:@"exists == true"];

    [self expectationForPredicate:existsPredicate evaluatedWithObject:element handler:nil];

    [self waitForExpectationsWithTimeout:timeout handler:^(NSError * _Nullable error) {
        if (error != nil) {
            NSString *message = [NSString stringWithFormat:@"Failed to find %@ after %f seconds",element,timeout];
            [self recordFailureWithDescription:message inFile:file atLine:line expected:YES];
        }
    }];
}

용법

XCUIElement *element = app.staticTexts["Name of your element"];
[self waitForElementToAppear:element withTimeout:5];

빠른

func waitForElementToAppear(element: XCUIElement, timeout: NSTimeInterval = 5,  file: String = #file, line: UInt = #line) {
        let existsPredicate = NSPredicate(format: "exists == true")

        expectationForPredicate(existsPredicate,
                evaluatedWithObject: element, handler: nil)

        waitForExpectationsWithTimeout(timeout) { (error) -> Void in
            if (error != nil) {
                let message = "Failed to find \(element) after \(timeout) seconds."
                self.recordFailureWithDescription(message, inFile: file, atLine: line, expected: true)
            }
        }
    }

용법

let element = app.staticTexts["Name of your element"]
self.waitForElementToAppear(element)

또는

let element = app.staticTexts["Name of your element"]
self.waitForElementToAppear(element, timeout: 10)

출처


1
위의 xcode9 예제와 관련된 더 많은 그림을 찾고 있습니다
rd_


1
테스트했습니다. 매력처럼 작동합니다! 감사!
Dawid Koncewicz

32

Xcode 8.3부터는 XCTWaiter http://masilotti.com/xctest-waiting/

func waitForElementToAppear(_ element: XCUIElement) -> Bool {
    let predicate = NSPredicate(format: "exists == true")
    let expectation = expectation(for: predicate, evaluatedWith: element, 
                                  handler: nil)

    let result = XCTWaiter().wait(for: [expectation], timeout: 5)
    return result == .completed
}

또 다른 요령은 wait함수 를 작성하는 것입니다. 크레딧은 John Sundell에게 전달하여 나에게 보여줍니다.

extension XCTestCase {

  func wait(for duration: TimeInterval) {
    let waitExpectation = expectation(description: "Waiting")

    let when = DispatchTime.now() + duration
    DispatchQueue.main.asyncAfter(deadline: when) {
      waitExpectation.fulfill()
    }

    // We use a buffer here to avoid flakiness with Timer on CI
    waitForExpectations(timeout: duration + 0.5)
  }
}

그리고 그것을 사용하십시오

func testOpenLink() {
  let delegate = UIApplication.shared.delegate as! AppDelegate
  let route = RouteMock()
  UIApplication.shared.open(linkUrl, options: [:], completionHandler: nil)

  wait(for: 1)

  XCTAssertNotNil(route.location)
}

11

를 기반으로 테드의 대답 @ ,이 확장 기능을 사용했습니다 :

extension XCTestCase {

    // Based on https://stackoverflow.com/a/33855219
    func waitFor<T>(object: T, timeout: TimeInterval = 5, file: String = #file, line: UInt = #line, expectationPredicate: @escaping (T) -> Bool) {
        let predicate = NSPredicate { obj, _ in
            expectationPredicate(obj as! T)
        }
        expectation(for: predicate, evaluatedWith: object, handler: nil)

        waitForExpectations(timeout: timeout) { error in
            if (error != nil) {
                let message = "Failed to fulful expectation block for \(object) after \(timeout) seconds."
                self.recordFailure(withDescription: message, inFile: file, atLine: line, expected: true)
            }
        }
    }

}

이렇게 사용할 수 있습니다

let element = app.staticTexts["Name of your element"]
waitFor(object: element) { $0.exists }

또한 요소가 사라지거나 다른 속성이 변경 될 때까지 기다릴 수 있습니다 (적절한 블록을 사용하여)

waitFor(object: element) { !$0.exists } // Wait for it to disappear

+1은 매우 빠르며 XCUIElements 등의 일부 속성을 기다리는 경우와 같이 표준 조건 자 표현식이 때때로 작동하지 않기 때문에 훨씬 더 나은 것으로 생각되는 블록 조건자를 사용합니다.
lawicko

10

편집하다:

실제로 Xcode 7b4에서 UI 테스트는 이제 expectationForPredicate:evaluatedWithObject:handler:

실물:

다른 방법은 설정된 시간 동안 런 루프를 돌리는 것입니다. 기다려야 할 시간 (예상) 시간을 알고있는 경우에만 유용합니다.

Obj-C : [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow: <<time to wait in seconds>>]]

빠른: NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate(timeIntervalSinceNow: <<time to wait in seconds>>))

테스트를 계속하기 위해 일부 조건을 테스트해야하는 경우에는 유용하지 않습니다. 조건부 검사를 실행하려면 while루프를 사용하십시오 .


이것은 깨끗하고 매우 유용합니다. 예를 들어 앱 실행 대기, 사전로드 된 데이터 요청 및 로그인 / 로그 아웃 작업을 수행합니다. 감사합니다.
felixwcf

4

다음 코드는 Objective C와 함께 작동합니다.

- (void)wait:(NSUInteger)interval {

    XCTestExpectation *expectation = [self expectationWithDescription:@"wait"];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [expectation fulfill];
    });
    [self waitForExpectationsWithTimeout:interval handler:nil];
}

아래 주어진대로이 함수를 호출하십시오.

[self wait: 10];

오류-> "NSInternalInconsistencyException", "API 위반-예상치를 설정하지 않고 대기하도록 호출되었습니다."가 발견되었습니다.
FlowUI. SimpleUITesting.com

@ iOSCalendarpatchthecode.com, 그 대안 솔루션을 찾았습니까?
Max

@Max이 페이지에서 다른 것을 사용할 수 있습니까?
FlowUI. SimpleUITesting.com

@ iOSCalendarpatchthecode.com 아니요, 확인해야 할 요소없이 약간의 지연이 필요합니다. 그래서 나는 이것의 대안이 필요합니다.
최대

@Max이 페이지에서 선택한 답변을 사용했습니다. 그것은 나를 위해 일했다. 어쩌면 구체적으로 원하는 것을 물어볼 수도 있습니다.
FlowUI. SimpleUITesting.com

4

내 경우에는 sleep부작용을 만들었으므로wait

let _ = XCTWaiter.wait(for: [XCTestExpectation(description: "Hello World!")], timeout: 2.0)

0

XCUIElement 용 API에 따르면 .exists쿼리가 존재하는지 여부를 확인하는 데 사용할 수 있으므로 다음 구문이 유용한 경우가 있습니다!

let app = XCUIApplication()
app.launch()

let label = app.staticTexts["Hello, world!"]
while !label.exists {
    sleep(1)
}

기대가 충족 될 것이라고 확신한다면 이것을 실행 해 볼 수 있습니다. waitForExpectationsWithTimeout(_,handler:_)@Joe Masilotti의 게시물 의 대기 시간이 너무 길면 충돌이 바람직 할 수 있습니다 .


0

잠이 스레드를 차단합니다

"스레드가 차단 된 동안에는 실행 루프 처리가 발생하지 않습니다."

waitForExistence를 사용할 수 있습니다

let app = XCUIApplication()
app.launch()

if let label = app.staticTexts["Hello, world!"] {
label.waitForExistence(timeout: 5)
}

0

스레드가 휴면 상태가되거나 시간 초과시 오류가 발생하지 않고 지연이 발생합니다.

let delayExpectation = XCTestExpectation()
delayExpectation.isInverted = true
wait(for: [delayExpectation], timeout: 5)

기대치가 반전되기 때문에 조용히 시간 초과됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.