Затримка / очікування в тестовому випадку тестування інтерфейсу 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.