หน่วงเวลา / รอในกรณีทดสอบของการทดสอบ Xcode UI


182

ฉันกำลังพยายามเขียนกรณีทดสอบโดยใช้การทดสอบ UI ใหม่ที่มีอยู่ใน Xcode 7 เบต้า 2 แอพนี้มีหน้าจอเข้าสู่ระบบซึ่งจะทำการโทรไปยังเซิร์ฟเวอร์เพื่อเข้าสู่ระบบ มีความล่าช้าที่เกี่ยวข้องกับสิ่งนี้เนื่องจากเป็นการดำเนินการแบบอะซิงโครนัส

มีวิธีที่จะทำให้เกิดความล่าช้าหรือรอกลไกใน XCTestCase ก่อนที่จะดำเนินการขั้นตอนต่อไปหรือไม่?

ไม่มีเอกสารที่เหมาะสมและฉันผ่านไฟล์ Header ของชั้นเรียน ไม่พบสิ่งที่เกี่ยวข้องกับสิ่งนี้

ความคิด / คำแนะนำใด ๆ


13
ฉันคิดว่าNSThread.sleepForTimeInterval(1)ควรจะทำงาน
Kametrixom

ที่ดี! ดูเหมือนว่าจะใช้งานได้ แต่ฉันไม่แน่ใจว่าเป็นวิธีที่แนะนำให้ทำหรือไม่ ฉันคิดว่า Apple ควรให้วิธีที่ดีกว่าในการทำเช่นนั้น อาจต้องยื่น Radar
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 Hmm น่าสนใจ; ฉันจะต้องตรวจสอบสิ่งนี้อีกครั้ง
Jedidja

1
มันไม่ทำงานสำหรับฉัน นี่คือตัวอย่างของฉัน: ให้ xButton = app.toolbars.buttons ["X"] ให้มีอยู่ = NSPredicate (รูปแบบ: "มีอยู่ == 1") ความคาดหวังสำหรับการจำลอง (มีอยู่ประเมินแล้วด้วย: xButton, ตัวจัดการ: ไม่มี) ไม่มี)
emoleumassi

app.launch()ดูเหมือนว่าจะเพียงแค่สังข์แอป จำเป็นหรือไม่
Chris Prince

225

นอกจากนี้คุณสามารถนอนหลับได้:

sleep(10)

เนื่องจาก UITests ทำงานในกระบวนการอื่นการทำงานนี้ ฉันไม่รู้ว่ามันแนะนำได้อย่างไร แต่มันได้ผล


2
บางครั้งเราต้องการหน่วงเวลาและไม่ต้องการให้มันล้มเหลว! ขอบคุณ
Tai Le

13
คำตอบที่ดีที่สุดที่ฉันเคยเห็น :) ฉันจะเพิ่ม + 100 คะแนนโหวตถ้าฉันทำได้ :)
BartłomiejSemańczyk

8
ฉันชอบ NSThread.sleepForTimeInterval (0.2) เนื่องจากคุณสามารถระบุความล่าช้าย่อยได้ (sleep () รับพารามิเตอร์จำนวนเต็มสามารถคูณได้เพียงเสี้ยววินาทีเท่านั้น)
Graham Perks

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

ใช่ฉันเขียนไว้ในพาดหัวด้านบน แต่ตอนนี้คนส่วนใหญ่ควรได้รับการอัพเกรดเป็นอย่างน้อย Xcode 9 ;-)
blackjacx

77

Xcode 9 แนะนำเทคนิคใหม่ด้วยXCTWaiter

กรณีทดสอบรออย่างชัดเจน

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_

คุณสามารถตรวจสอบshashikantjagtap.net/asynchronous-ios-testing-swift-xcwaiter
Ted

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

จากคำตอบของ @ Tedฉันได้ใช้ส่วนขยายนี้:

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 swifty มากและใช้ block predicate ซึ่งฉันคิดว่าดีกว่ามากเพราะนิพจน์เพรดิเคตมาตรฐานไม่ทำงานสำหรับฉันในบางครั้งตัวอย่างเช่นเมื่อรอคุณสมบัติบางอย่างใน 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 คุณสามารถใช้คนอื่น ๆ ในหน้านี้ได้หรือไม่?
FlowUI SimpleUITesting.com

@ iOSCalendarpatchthecode.com ไม่ฉันแค่ต้องการความล่าช้าโดยไม่ต้องตรวจสอบองค์ประกอบใด ๆ ดังนั้นฉันต้องการทางเลือกนี้
สูงสุด

@ Max ฉันใช้คำตอบที่เลือกในหน้านี้ มันใช้งานได้สำหรับฉัน บางทีคุณสามารถถามพวกเขาในสิ่งที่คุณกำลังมองหาโดยเฉพาะ
FlowUI SimpleUITesting.com

4

ในกรณีของฉันsleepสร้างผลข้างเคียงดังนั้นฉันจึงใช้wait

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

0

ตาม API สำหรับ XCUIElement .existsสามารถใช้เพื่อตรวจสอบว่ามีแบบสอบถามหรือไม่ดังนั้นไวยากรณ์ต่อไปนี้อาจเป็นประโยชน์ในบางกรณี!

let app = XCUIApplication()
app.launch()

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

หากคุณมั่นใจว่าความคาดหวังของคุณจะได้รับการตอบสนองในที่สุดคุณสามารถลองใช้งานได้ ควรสังเกตว่าการหยุดทำงานอาจดีกว่าหากการรอนานเกินไปซึ่งwaitForExpectationsWithTimeout(_,handler:_)ควรใช้โพสต์ของ @Joe Masilotti


0

sleep จะบล็อกเธรด

"ไม่มีการประมวลผลลูปเรียกใช้เกิดขึ้นในขณะที่เธรดถูกบล็อก"

คุณสามารถใช้ 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.