0
Обложка: Как сделать автотесты на Swift комфортными для тестировщиков

Как сделать автотесты на Swift комфортными для тестировщиков

В 2020 году перед нами была поставлена задача по написанию автотестов на мобильные платформы. Наши наработки планировалось использовать, как вектор развития автотестов для всех проектов компании, что стало дополнительным стимулом подойти к вопросу ответственно.

Игорь Бессараб
Игорь Бессараб
Lead Automation QA в IW Group

Вводные данные

Итак, наше «дано» на момент поиска решения:

  • Команда тестирования с опытом написания автотестов для web на Java;
  • Талантливые и амбициозные iOS и Android разработчики в непосредственном доступе;
  • Довольно сжатые сроки.

Что мы искали в автотестах:

  • Поддерживаемость;
  • Оптимальный порог вхождения;
  • Стабильность;
  • Надежность.

Нас очень привлекала возможность осваивать нативные автотесты. Отталкивал только более высокий порог вхождения. Но, когда в горнило наших скиллов угодил Android, вопрос отпал сам собой.

На первых же тестовых классах стало понятно, что это то, что мы искали, тесты писались быстро, были лаконичными и читаемыми, прогоны не падали из-за внутренних проблем софта.

Какие технологии мы выбрали

Из тестовых фреймворков мы выбрали Kaspresso, который сочетал в себе все фичи Espresso и Kakao, обладая целой кучей собственных улучшений и доработок. Буквально за полтора месяца работы процент покрытия приложения автотестами превысил 90%.

За плечами осталась база Kotlin и, на мой взгляд, лучший фреймворк для тестирования на Android — Kaspresso.

В разработке Kotlin и Swift очень похожи, но, к сожалению, все сходства сводятся на нет при написании нативных автотестов.

Обычно тестовые фреймворки рассчитаны на базовый уровень знаний языка, как тот же Kaspresso или Selenide. Но оказалось, что XCTest не хочет нас видеть без должного уровня в iOS разработке, и на помощь были вызваны iOS-разработчики.

Очевидно, что в «яблочной» компании есть своё видение того, кто может прикасаться к коду для их приложений, и в список этих людей не входят тестировщики. В результате, мы нос к носу столкнулись с проблемой порога вхождения.

Со стороны iOS-разработчиков поступило предложение быстро надстроить «что-нибудь красивое» над XCUITest. Мы подготовили список того, что нам нужно делать и с какими элементами.

Выкрутив красноречие и харизму на максимум, и стараясь не произносить фразу «Сделайте как у Android», договорились чтобы сделать тестовые методы похожими на знакомый нам Kaspresso. Наши iOS-разработчики принялись паковать локаторы в более знакомые методы, и уже в первый же день все начало приобретать более изящный вид.

Улучшаем автотесты

Так выглядел локатор по тексту на XCUITest:

let predicate = NSPredicate(format: "label CONTAINS 'Да'")
lazy var yesBtn = XCUIApplication().buttons.containing(predicate).firstMatch

Упаковали его в:

lazy var yesBtn = UtilCom.elementWithText(type: .button, text: "Да")

Тут самой главной сложностью были типы. Привычка из Kaspresso, если мы в тесте указываем, что ищем кнопку с id «any_id», Kaspresso не будет «прокидывать» ошибку, что он нашел id, но это не кнопка. Он просто скажет «Ну допустим это кнопка, хотите нажмем?». На XCUITest так не получалось, любая ошибка в типе элемента неизменно вела к падению тестов, и приходилось быстро и правильно искать по коду типы элементов.

Вторая проблема была с самими id — для разработки они не нужны и соответственно не проставлялись. Сами модификаторы accessibilityIdentifier или accessibility(identifier:), приходилось проставлять в процессе написания автотестов. А нужно это для того, чтобы максимально использовать более надежные локаторы по id, с которыми мы тоже поработали.

Было:

lazy var yesBtn = XCUIApplication().buttons["yesBtn"].firstMatch

Сделали:

lazy var yesBtn = UtilCom.elementWithId(type: .button, id: "yesBtn")

Также были переработаны более сложные локаторы, например ячейки в таблице из вида:

let tableOrder = 0
let cellIndex = 1
let table = XCUIApplication().tables.element(boundBy: tableOrder).firstMatch
let cell = table.cells.element(boundBy: cellIndex)
//cell.doSomething

Преобразились в:

UtilCom.tableCellWith(index: 1)

Важно отметить, что в тестах в xcode есть режим записи, который запишет все действия и преобразует в код, как это делает Appium. Но, как и с Appium, тут не все так просто, код все равно нужно будет дорабатывать, и данный метод не очень хорошо показывал себя в автотестах, написанных на этапе разработки и внесения изменений (теряется путь к элементу, приходится переписывать локаторы).

На момент завершения подготовки тестовых методов и локаторов, мы уже делали расширения для методов без помощи команды разработки. С совсем простыми типа isDisplayed/isNotDisplayed мы расправились сравнительно быстро. Но оказалось, что метод clearText, обычно базовый для большинства тестовых фреймворков, в целом не предусмотрен в XCUITest и, чтобы очистить поле ввода, надо «накодить» что-то типа этого:

lazy var someTextField = XCUIApplication().textFields["someTF"].firstMatch
    func clearSomeThing() {
    guard let stringValue = someTextField.value as? String else {
            return
     }
    let lowerRightCorner = someTextField.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.9))
                lowerRightCorner.tap()
    var deleteString = String()
    for _ in stringValue {
            deleteString += XCUIKeyboardKey.delete.rawValue
     }
    someTextField.typeText(deleteString)
    }

В целом мы так и «накодили», а потом запаковали все это в расширение и получили:

lazy var someTextField = UtilCom.elementWithId(type: .textField, id: "someTF")
    func clearSomeThing() {
    someTextField.clearText()

Подводя итоги

В итоге нашей совместной работы, за пару дней были написаны дополнительные методы, новые локаторы и расширения. Плюсом — еще около 60% покрытия приложения автотестами за все те же полтора месяца. Стабильными, быстрыми, не привязанными к X-Path и зашитыми прямо в код. Ну и конечно прекрасный тимбилдинг и взлет по скиллам.

А уж когда мы вышли на начало новых проектов, наши возможности вскружили головы всем скептикам. Мы находили уязвимости там, где их практически невозможно было заметить мануально, мы всегда могли подстраховать в плане сборок (собрать/пересобрать локально), а автотесты писались практически параллельно с разработкой.

С появлением новых задач и проектов на Swift UI наш «фреймворк» разрастается и пополняется.

Что мы получили в итоге:

  • Простые в написании и с низким порогом вхождения автотесты;
  • Стабильность и надежность тестов;
  • Большее вовлечение команды тестирования в проект;
  • Навыки программирования на Swift (рано или поздно все равно приходится править методы).

Минусы нашего подхода:

  • Приходилось задействовать команду разработки на не совсем профильные задачи.

Наверное для каждого проекта должна быть своя оптимальная практика автотестирования. Мы сталкивались с проектами, где автотестирование было только на бэке, а фронт так часто менялся, что любые автотесты были просто бесполезны.

Позже мы столкнулись с Python+Appium и тоже не были разочарованы. Но на момент описанных выше событий выбранная стратегия показала себя максимально продуктивной и до сих про остается нашей любимой.