Затримка / очікування в тестовому випадку тестування інтерфейсу Xcode


185

Я намагаюся написати тест, використовуючи нове тестування інтерфейсу, доступне в бета-версії Xcode 7. Додаток має екран входу, де він здійснює виклик на сервер для входу. З цим пов’язана затримка, оскільки це асинхронна операція.

Чи є спосіб викликати механізм затримки або очікування в XCTestCase, перш ніж переходити до подальших кроків?

Немає належної документації, і я переглянув файли заголовків класів. Не вдалося знайти нічого пов’язаного з цим.

Будь-які ідеї / пропозиції?


13
Я думаю, що це NSThread.sleepForTimeInterval(1)має спрацювати
— Каметріксом,

Чудово! Здається, це працює. Але я не впевнений, чи рекомендується це робити. Я думаю, що Apple повинна запропонувати кращий спосіб це зробити. Можливо, доведеться подати радар
— Tejas HS

Я насправді думаю, що це нормально, це справді найпоширеніший спосіб призупинити поточний потік на певний час. Якщо ви хочете отримати більше контролю, ви також можете потрапити до GCD (The dispatch_after, dispatch_queuestuff)
— Каметрікс

@Kametrixom Не відзначайте цикл запуску - Apple представила власне асинхронне тестування в бета-версії 4. Докладніше див. У моїй відповіді .
— Джо Масілотті

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

Відповіді:


170

Асинхронне тестування інтерфейсу користувача було введено в Xcode 7 Beta 4. Дочекатися мітки з текстом "Привіт, світе!" щоб з’явитись, ви можете зробити наступне:

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)

Детальніше про тестування користувацького інтерфейсу можна знайти в моєму блозі.


20
На жаль, ми не можемо визнати, що тайм-аут стався, і рухатись далі - waitForExpectationsWithTimeoutце автоматично провалить ваш тест, що досить шкода.
— Jedidja

@Jedidja Насправді, це не відбувається у мене з XCode 7.0.1.
— Бастіан

@Bastian Хм цікаво; Мені доведеться перевірити це.
— Джедіджа

1
у мене це не працює. Ось мій зразок: let xButton = app.toolbars.buttons ["X"] let Існує = NSPredicate (формат: "Існує == 1") expectationForPredicate (існує, оцінюєтьсяWithObject: xButton, обробник: nil) waitForExpecationsWithTimeout (10, обробник: nil)
— emoleumassi

app.launch(), Здається, просто перезапустити програму. Це необхідно?
— Chris Prince

228

Крім того, ви можете просто спати:

sleep(10)

Оскільки UITests працюють в іншому процесі, це працює. Не знаю, наскільки це доцільно, але це працює.


2
Якийсь час нам потрібен спосіб затримки і не хочемо, щоб це спричинило невдачу! подяка
— Тай Ле

8
Мені подобається NSThread.sleepForTimeInterval (0,2), оскільки ви можете вказати затримки в секунду. (sleep () приймає цілочисельний параметр; можливі лише кратні секунди).
— Грем Перкс

5
@GrahamPerks, так, хоча є також:usleep
— mxcl

2
Я хотів би, щоб була краща відповідь, але це, здається, єдиний спосіб зараз, якщо ви не хочете спричинити невдачу.
— Chase Holland

3
Це не погана пропозиція (ви не розумієте, як працює UITesting), але навіть якщо це була погана пропозиція, іноді немає можливості сформувати очікування, яке працює (система попереджає когось?), Отже, це все, що у вас є.
— mxcl

81

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

Завдання С

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

Ви можете перевірити shashikantjagtap.net/asynchronous-ios-testing-swift-xcwaiter
— Ted

1
Перевірено Працює як оберіг! Дякую!
— Давид Концевич,

34

Станом на 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функцію, заслуга в тому, що Джон Санделл показав її мені

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

12

На основі відповіді @ Теда я використав це розширення:

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."
                let location = XCTSourceCodeLocation(filePath: file, lineNumber: line)
                let issue = XCTIssue(type: .assertionFailure, compactDescription: message, detailedDescription: nil, sourceCodeContext: .init(location: location), associatedError: nil, attachments: [])
                self.record(issue)
            }
        }
    }

}

Ви можете використовувати його так

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 тестування інтерфейсу зараз є 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

5

У моєму випадку sleepстворений побічний ефект, тому я використовувавwait

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

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];

Помилка -> виявлено "NSInternalInconsistentcyException", "Порушення API - виклик зроблений для очікування без встановлення будь-яких сподівань"
— FlowUI. SimpleUITesting.com

@ iOSCalendarpatchthecode.com, Ви знайшли альтернативне рішення для цього?
— Макс.

@Max, чи можеш ти використати будь-який інший на цій сторінці?
— FlowUI. SimpleUITesting.com

@ iOSCalendarpatchthecode.com Ні, мені просто потрібна певна затримка без будь-якого елемента для перевірки. Тож мені потрібен альтернативний варіант цього.
— Макс.

@Max я використав вибрану відповідь на цій сторінці. У мене це спрацювало. Можливо, ви можете запитати їх, що конкретно ви шукаєте.
— FlowUI. SimpleUITesting.com

3

Це створить затримку без переведення потоку в режим сну або виникнення помилки під час очікування:

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

Оскільки очікування обернене, спокійно очікує очікування.


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)
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.