Привет, Хабр!
В UI тестах не все события происходят синхронно друг за другом. Сетевые запросы, анимации, чтение файлов – все это требует времени. Встает вопрос – как заставить тест ждать наступления определенного события?
В этой статье мы разберем:
Что такое ожидание и для чего они используются в тестах;
Рассмотрим какие бывают ожидания;
Разберем из чего состоят ожидания;
Рассмотрим пример эффективных ожиданий в тестах.
Что такое ожидание и для чего они используются в тестах
Ожидания — это методы, которые ждут выполнения определенного условия или истечения определенного времени. Так как элементы на экране появляются не всегда моментально, нам необходимо использовать задержки иначе код отвечающий за действия не будет работать, потому что элемента ещё нет на экране.
Ожидания бывают двух типов:
Явные ожидания;
Неявные ожидания.
Разберем подробно каждый тип.
Явные ожидания
Явные ожидания — это код, который ждет наступления какого-то события, прежде чем продолжит выполнение команды скрипта. Такое ожидание срабатывает один раз в указанном месте.
Пример:
// Ждем 5 секунд
Thread.sleep(forTimeInterval: 5)
Сразу скажу, что использование явных ожиданий — это моветон в автоматизации. Использование явных задержек не гарантирует наступление нужного события. Или будет слишком избыточным и увеличит время выполнения теста.
Неявные ожидания
Неявные ожидания — это код, который делает многократные попытки найти элемент на экране в течение заданного периода времени, если элемент не найден сразу.
Пример:
// Ждём в течении 5 секунд что кнопка есть на экране. Если кнопка отобразится на 2 секунде, ожидание выполнится и мы пойдем дальше
let element = XCUIApplication().buttons["testButton"]
let predicate = NSPredicate(format: "exists == true")
let expectation = XCTNSPredicateExpectation(predicate: existsPredicate,
object: element)
wait(for: [expectation], timeout: 5)
Условия выполнения ожидания
Прежде чем написать ожидание, мы должны создать условия выполнения этого ожидания. В этом нам помогут следующие классы:
XCTNSPredicateExpectation — ожидание, которое проходит, когда выполняется NSPredicate, самый гибкий вариант. Статья с хорошим объяснением, как использовать NSPredicate
let element = XCUIApplication().buttons["testButton"]
let predicate = NSPredicate(format: "exists == true")
// В этом условии выполнения ожидания мы ждем, что кнопка, иницилизированная выше, существует
let expectation = XCTNSPredicateExpectation(predicate: existsPredicate,
object: element)
XCTKVOExpectation — ожидание, которое проходит, когда выполняется условие наблюдения за ключевыми значениями(KVO);
let element = XCUIApplication().buttons["testButton"]
// В этом условии выполнения ожидания мы ждем, что кнопка, иницилизированная выше, существует
let expectation = XCTKVOExpectation(keyPath: "exists",
object: element,
expectedValue: true)
XCTNSNotificationExpectation — ожидание, которое проходит при получении уведомления от NSNotificationCenter;
let element = XCUIApplication().buttons["testButton"]
// В этом условии выполнения ожидания мы ждем когда NSNotification отправится из центра уведомлений
let expectation = XCTNSNotificationExpectation(name: NSNotification.Name(rawValue: "exist"), object: element)
XCTDarwinNotificationExpectation — ожидание, которое выполняется при получении ожидаемого уведомления Darwin;
let expectation = XCTDarwinNotificationExpectation(notificationName: "DarwinNotificationName")
Ожидания в XCTest
Написать эффективные ожидания мы можем несколькими способами. Покажу два самых распространенных и эффективных способа:
XCTestCase Wait
Для использования этого ожидания, вам потребуется наследовать ваш класс с тестами от класса XCTestCase. У этого класса уже есть метод wait(), который можно использовать в своих тестах.
func wait(for expectations: [XCTestExpectation], timeout seconds: TimeInterval)
Метод принимает массив с условиями выполнения ожидания и синхронно ожидает каждое из них в заданном порядке в течение заданного количества времени.
Пример:
let element = XCUIApplication().buttons["testButton"]
let existsPredicate = NSPredicate(format: "exists == true")
let expectation = XCTNSPredicateExpectation(predicate: existsPredicate,
object: element)
wait(for: [expectation], timeout: 3)
XCTAssert(element.exists)
XCTWaiter
_ = XCTWaiter.wait(for: [expectation1, expectation2],
timeout: TimeInterval(timeoutValue))
На первый взгляд можно сказать, что этот метод работает также, как и в примере выше. Но в них есть небольшое различие: данный метод возвращает перечисление XCTWaiter.Result. XCTWaiter.Result — это состояния, при которых прекратилось ожидание, разберем их:
completed — ожидания были успешно выполнены;
timedOut — истекло время до выполнения ожиданий;
incorrectOrder — ожидания оправдались в порядке отличном от заданого;
invertedFulfillment — ожидания выполнились в обратном порядке;
interrupted — выполнение ожидания было прервано до выполнения условий или до того как истекло заданное время.
Благодаря этому можно более гибко подходить к анализу отчетов после прогона тестов и добавлять необходимы логи, которые помогут в локализации сбоя работы автотестов.
Ожидание реализованное с XCTWaiter:
func waitForExpectation(expectation:[XCTestExpectation],
time: TimeInterval) {
let result: XCTWaiter.Result = XCTWaiter().wait(for: [expectation],
timeout: time)
switch result {
case .timedOut:
XCTFail("Condition was not satisfied during \(time) seconds")
case .interrupted:
XCTFail("The waiter was interrupted prior to its expectations being fulfilled or timing out")
case .incorrectOrder:
XCTFail("The waiter’s expectations were not fulfilled in the required order")
case .invertedFulfillment:
XCTFail("An inverted expectation was fulfilled")
default:
break
}
}
Самое важное:
Есть два вида ожиданий: явные и неявные;
Используйте неявные ожидания, и тогда ваши тесты будут работать стабильно и быстро;
Используйте в качестве условия выполнения ожидания XCTNSPredicateExpectation. Это самый гибкий вариант из представленных;
Используйте XCTWaiter, так как он даёт больше информации при локализации ошибки.
Это последняя статья в цикле статей, но на этом мы не перестаем делиться с вами опытом автоматизации. Напишите в комментариях, на какую тему по iOS автоматизации вам было бы интересно почитать статью.
RileyUsagi
Было бы здорово почитать про архитектуру ui-фреймворка, который использует в себе все вышеописанные особенности.
Boris_Lys Автор
У нас в планах написать большую статью, как устроена мобильная автоматизация на проекте. Там будет про архитектуру нашего фреймворка