Как ускорить тесты пользовательского интерфейса в Xcode?

Начиная с Xcode 7 у нас есть хороший API для тестирования пользовательского интерфейса. В основном меня это устраивает. Единственное беспокойство связано со скоростью.

Вначале обычный тестовый пример пользовательского интерфейса (около 15 действий) выполнялся примерно 25 секунд. Тогда я полностью высмеял сети. Теперь это занимает 20 секунд. Учитывая тот факт, что время занимает только анимация и время запуска (1 секунда или даже меньше), я предполагаю, что должен быть способ ускорить его.


person Artem Stepanenko    schedule 17.05.2016    source источник


Ответы (8)


Попробуйте установить это свойство при запуске тестов пользовательского интерфейса:

UIApplication.shared.keyWindow?.layer.speed = 100

Вот как я это установил:

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    if ProcessInfo.processInfo.arguments.contains("UITests") {
        UIApplication.shared.keyWindow?.layer.speed = 100
    }
}

И в моих тестах пользовательского интерфейса:

class MyAppUITests: XCTestCase {

    // MARK: - SetUp / TearDown

    override func setUp() {
        super.setUp()

        let app = XCUIApplication()
        app.launchArguments = ["UITests"]
        app.launch()
    }
}

В этом сообщении в блоге есть еще несколько полезных советов. .

person Mark    schedule 18.05.2016
comment
Спасибо за ответ. Оно работает! Чтобы немного улучшить: можно ли увеличить скорость анимации из процесса тестирования пользовательского интерфейса? - person Artem Stepanenko; 18.05.2016
comment
Нет, к сожалению. Предполагается, что процесс тестирования пользовательского интерфейса полностью отделен от вашего приложения (и взаимодействует только с помощью специальных возможностей и аргументов запуска). - person Mark; 18.05.2016
comment
@ArtemStepanenko, вы можете увеличить скорость анимации в процессе тестирования пользовательского интерфейса, используя SBTUITestTunnel. Мы разработали эту библиотеку, чтобы обеспечить взаимодействие между приложением и тестовой целью. - person Tomas Camin; 19.05.2016
comment
@Mark Привет! Куда вы это положили? В вашем AppDelegate или внутри UI Test перед запуском приложения? Спасибо. - person Anas; 17.10.2016
comment
Я передаю переменную среды своему приложению из целевого объекта тестирования пользовательского интерфейса, и в didFinishLaunching я проверяю эту переменную, а затем вызываю приведенный выше код. Я могу обновить свой ответ, добавив больше контекста. - person Mark; 18.10.2016
comment
Извините, аргумент запуска, а не переменная среды. - person Mark; 18.10.2016
comment
почему вы ставите скорость на 100, а не на 10 000? - person Blazej SLEBODA; 21.07.2020
comment
Просто произвольное значение. 100 ускорений достаточно для моих нужд. - person Mark; 22.07.2020

Другая возможность - вообще отключить анимацию:

[UIView setAnimationsEnabled:NO];

Swift 3:

UIView.setAnimationsEnabled(false)
person Artem Stepanenko    schedule 24.05.2016
comment
Это круто! Большое спасибо, что поделились. Мой набор тестов теперь работает со скоростью света. - person Rudolf Adamkovič; 16.06.2016
comment
Вы не должны полностью отключать анимацию, так как вы можете не отловить некоторые ошибки, конкретно связанные с анимацией. Дополнительную информацию можно найти в этом отличном блоге записи. - person Tomas Camin; 17.06.2016

После ответа @Mark версия Swift 3:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    if ProcessInfo.processInfo.arguments.contains("UITests") {
        UIApplication.shared.keyWindow?.layer.speed = 200
    }
}

В вашем тестовом файле пользовательского интерфейса:

override func setUp() {
    super.setUp()

    // Put setup code here. This method is called before the invocation of each test method in the class.

    let app = XCUIApplication()
    app.launchArguments = ["UITests"]
    app.launch()
person mourodrigo    schedule 24.02.2017

Добавьте это в didFinishLaunch

[UIApplication sharedApplication].keyWindow.layer.speed = 2;

Значение по умолчанию - 1, сделайте 2, чтобы удвоить его скорость.

person Shelly Pritchard    schedule 09.06.2017
comment
Вы можете объяснить, почему ваше решение лучше других? - person Noel Widmer; 09.06.2017

Я хотел отключить ВСЕ анимации во время тестирования снимков. Мне удалось добиться этого, отключив анимацию Core Animation и UIView, как показано ниже.

Обратите внимание, поскольку мое приложение использовало раскадровки, UIApplication.shared.keyWindow при запуске было равно нулю, поэтому я обращаюсь к UIWindow, напрямую ссылаясь на свойство window.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    if ProcessInfo.processInfo.arguments.contains("SnapshotTests") {
        // Disable Core Animations
        window?.layer.speed = 0
        // Disable UIView animations
        UIView.setAnimationsEnabled(false)
    }
    return true
}
person Oliver Pearmain    schedule 28.01.2019

Запустите их параллельно.

Если у вас только 1 машина для сборки, вы можете использовать Bluepill: https://github.com/linkedin/bluepill

Если у вас несколько компьютеров, вы можете использовать Emcee: https://github.com/avito-tech/Emcee (также работает для настройки на одной машине)

У нас есть 50 часов тестов пользовательского интерфейса, и Emcee позволяет нам запускать их за 1 час. Есть несколько уловок, позволяющих ускорить тесты пользовательского интерфейса, но это бессмысленно, если вы не запускаете их параллельно. Например. вы не можете запустить 25-секундные тесты за 0,5 секунды.


Трюки:

  1. Запустите XCUIApplication, не переустанавливая его:
XCUIApplication(
    privateWithPath: nil,
    bundleID: "your.bundle.id"
)

Примечание: вы должны запускать приложение с XCUIApplication().launch() хотя бы один раз за запуск XCUI test runner, чтобы установить приложение. Это требует использования частного API.

  1. Вы можете переместить что-нибудь в фоновый поток. Например.
let someDataFromApi = getSomeData() // start request asynchronously and immediately return
launchApp() // this can be few seconds
useInTest(someDataFromApi) // access to data will wait request to finish

Вы можете сделать так, чтобы код выглядел так, будто нет асинхронных вещей, это было бы проще для QA. Мы хотели реализовать это, но не сделали этого, так что это всего лишь идея.

  1. Переключитесь на EarlGrey и сделайте тесты, которые продлятся несколько секунд (но они не будут черным ящиком).

  2. Открывайте экраны через глубокие ссылки, если у вас есть глубокие ссылки.

  3. Используйте много частных API и других хаков. Например: вместо перехода через пользовательский интерфейс в приложение настроек и последующего сброса настроек конфиденциальности вы можете вызвать какой-нибудь частный API.

  4. Никогда не спите долго. Используйте опрос.

  5. Ускорьте анимацию. Не отключайте их!

  6. Передайте некоторые флаги из тестов пользовательского интерфейса в приложение, чтобы пропустить начальные предупреждения / руководства / всплывающие окна. Можно сэкономить много времени. Обязательно проведите хотя бы один тест, который проверяет, работают ли эти предупреждения.

person artyom.razinov    schedule 03.02.2019
comment
Привет, с 2020 года, это хороший ответ, и я начал настраивать свою настройку bluepill, однако это сэкономило бы мне время, чтобы узнать, что параллельное тестирование теперь поддерживается изначально в Xcode, нет необходимости в сторонних инструментах, по крайней мере, установка 1 машины . - person lawicko; 18.12.2020

Я уменьшаю время UITests на 30%, выполняю все шаги:

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

let app = XCUIApplication()

override func setUp() {
    super.setUp()

    continueAfterFailure = false
    app.launchArguments += ["--Reset"]
    app.launch()
}

Теперь в вашем AppDelegate добавьте:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    setStateForUITesting()
}

static var isUITestingEnabled: Bool {
    get {
        return ProcessInfo.processInfo.arguments.contains("--Reset")
    }
}

private func setStateForUITesting() {
    if AppDelegate.isUITestingEnabled {
        // If you need reset your app to clear state
        UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)

        // To speed up your tests
        UIApplication.shared.keyWindow?.layer.speed = 2
        UIView.setAnimationsEnabled(false)
    }
}

В вашем коде, чтобы проверить, находится ли он в тестовом режиме, вы можете использовать:

if AppDelegate.isUITestingEnabled {
    print("Test Mode")
}

Кроме того, чтобы можно было wait во время загрузки элемента, я создал это расширение:

import XCTest

extension XCUIElement {
    func tap(wait: Int, test: XCTestCase) {
        if !isHittable {
            test.expectation(for: NSPredicate(format: "hittable == true"), evaluatedWith: self, handler: nil);
            test.waitForExpectations(timeout: TimeInterval(wait), handler: nil)
        }
        tap()
    }
}

Используйте так:

app.buttons["start"].tap(wait: 20, test: self)
person Narlei Moreira    schedule 29.05.2019
comment
Спасибо, что поделился! Я уверен, что на это потребовалось время и усилия. Было бы еще полезнее для всех, если бы вы сначала прочитали другие ответы, чтобы убедиться, что вы не повторяете то, что уже сказали другие. Ваше здоровье. Молодец! - person Artem Stepanenko; 31.05.2019

Обратите внимание, что keyWindow устарел с iOS 13. В частности, UIApplication.sharedApplication.keyWindow и self.window.keyWindow в AppDelegate оба возвращают nil. В этом ответе говорится, что нужно пройти через UIApplication.shared.windows и найти тот, у которого isKeyWindow true, но в моем тесте с использованием ios14 даже этот ответ возвращает false для моего единственного окна. В итоге установка self.window.layer.speed у меня сработала.

person qix    schedule 07.06.2021