Автотестирование в iOS. Юнит-тесты и UI Automation

На автотестирование всегда чего-то не хватает. Жалко времени, жалко денег. Действительно, часто соотношение качества работы и затраченного времени — достаточно, чтобы автотесты не делать (полагаясь на традиционное тестирование). Ангстрем жил без тестов почти год, но после того, как в очередном апдейте «внезапно» пропала русская клавиатура, я сдался и организовал несколько тысяч тестов, покрывающих почти всю основную функциональность приложения.

Что такое автотест?

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

Потыкать в разные части экрана всеми пальцами сразу и посмотреть, чтобы ничего не сломалось.

А вот четкую последовательность действий можно оформить это в виде небольшой программы:

Нажать на клавиатуре кнопки «1», «м». Проверить, что справа получилось «3,28 ft».

Запуская тесты после изменения приложения, легко проверить, что всё работает, как требуется.

Типы тестов

Для Ангстрема пришлось использовать несколько разных типов тестов:

  • Юнит-тесты, которые проверяют совсем базовые вещи.
  • UI-тесты, имитирующие тыки в Айфон, после чего проверяется правильность результата.
  • Полуавтоматические тесты, которые после протыкивания делают скриншот, проверить который должен буду я сам.
  • Тесты на производительность, которые проверяют, что не затормозился старт приложения, что ввод букв не требует большого времени из-за подгрузки индексов.

Тесты элементарной функциональности (юнит-тесты)

Юнит-тесты работают с кодом. В идеале берется каждый метод, и пишется тест, подающий разные значения параметров и проверющий корректность результата. К примеру, сравнительно легко так протестировать функцию умножения. И, наоборот, проверить метод, запускающий анимацию вьюшки — трудно. При разработке iOS-приложений часто получается, что 80% приложения — это UI, на который нет чёткого ТЗ и на создание которого очень мало времени. В таких условиях тесты писать тяжело.

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

Юнит-тестами я проверяю:

  • Конвертирование единиц, чтобы метр всегда был равен ста сантиметрам.
  • Приоритет единиц в выдаче, чтобы по латинской «c» — градусы Цельсия, а не скорость света.
  • Форматирование отображения единиц. Чтобы большие/маленькие числа были со степенями, а валюты — с суффиксами, обозначающими размерность. Чтобы тысячи были разделены пробелами, чтобы время показывалось правильно.
  • Список групп единиц в меню, последовательность самих единиц в меню.
  • Автоподбор «правой» единицы по левой, чтобы футы по-умолчанию переводились в метры, а не в парсеки.
  • Диктовка и парсинг строки из буфера обмена.

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

К каждому тесту я создал специальный файл с проверками, чтобы можно было легко их добавлять и исправлять. Вот, например, кусок JSON-файла с тестами диктовки и парсинга буфера обмена (слева — текст, который проверяется, справа — корректный результат):

"$200": "200 $ (USA Dollar)"

"200$": "200 $ (USA Dollar)"

"семь градусов Цельсия": "7 °C (degree Celsius)"

UI-тесты

В 2010 году Эпл показала новое средство тестирования приложений, которое называется UI Automation. К сожалению, тогда «виртуальные тыки» не работали с локализованным интерфейсом и плохо работали с его динамическими изменениями, поэтому использование пришлось отложить. Сейчас проблем с локализацией нет.

Я попробовал UI Automation для:

  • Автоматического создания скриншотов приложения на разных языках и для разных устройств (для Апстора).
  • Автоматического создания видео приложения (для него же).
  • Непосредственно тестирования функциональности.

Скриншоты сделать удалось. Нужно еще автоматически подставлять туда правильную батарейку и все вообще будет классно.

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

Тесты получились почти все. Некоторые, также, как и видео, требуют сложной работы с интерфейсом, поэтому пока не работают.

Чтобы их проще было писать и запускать (пишутся они на JavaScript'е, запускаются из Терминала), я подготовил:

  • Специальные «заголовочные» файлы, которые включаются в каждый js-файл тестов и предоставляют стандартные средства («нажми на кнопку», «сделай свайп», «проверь, что результат такой»). В результате, обычные тесты теперь пишутся в одну строку, даже если они реализуют достаточно сложную логику ввода. Например, так: _testCase(["1m ft", "⌫"], "1 [meter] = 3.28 [foot]", "To").
  • Скрипты для авто-запуска тестов, чтобы можно было запустить тесты в терминале, проанализировать вывод и если, например, нет строк, начинающихся с "Fail:", то сказать, что все тесты пройдены.
  • Само приложение тоже потребовалось доработать, чтобы стало возможным из JavaScript'а «нажимать» на кнопки (об этом в разделе про Accessibility), и чтобы результат тестов можно было проверить.

Вместе с багами в самом UI Automation, было несколько больших проблем.

  • Работа с буфером обмена. Чтобы её сымитировать, пришлось завести ненастоящий и почти невидимый (совсем невидимый не редактируется) UITextField и вставить туда то, что скопировано, после чего проверить вставленное. Кстати, я узнал, что минимально возможный размер UITextField, в который что-то можно ввести — 2х2 поинта. Почему так, интересно?

  • Нажатие на конкретное место в большом контроле. Это пришлось реализовать для цифровой клавиатуры. Она сделана не несколькими кнопками, а одним большим контролом, который обрабатывает нажатия (так можно точнее контролировать внешний вид). Впрочем, для коротких тапов все сравнительно просто. А вот лонг-тап сейчас (благодаря целой группе недоработок в UI Automation) делается только в центре контрола. Так что минус (который получается долгим тапом на точку на цифровой клавиатуре) у меня так и не протестирован.

  • Пожалуй, самая большая проблема заключается в том, как проверить правильность результата тыков и свайпов. С экранных контролов считывать данные плохо, они зависят от языка. Мне же нужно проверить отдельно то, как отображаются единицы (это частично проверяется юнит-тестами и будет проверять человек), и то, как происходит преобразование. Выкрутился я, опять же, фейковым UILable'ом. Поставив несколько таких лейблов (невидимых, за пределами экрана), я проставляю в них специальные тестовые строки, которые точно отражают то, что я вижу на экране. Считать строку из лейбла не составляет труда, после чего можно сравнить её с тестовой.

В результате получилось протестировать почти всё:

  • Ввода чисел, единиц. Теперь уже не программно, а физически тыкая в кнопки. Так тестируется работа backspace, всех кнопок на цифровой клавиатуре.
  • Тестирование свинга.
  • Тестирование конвертирования единиц, включая нетривиальные способы ввода отрицательных чисел, разные типы единиц и так далее.
  • Тестирование копирования текста в буфер обмена.

Фейковые лейблы даже позволили немного протестировать преобразование валют и времени. С ними всё нетривиально, так как курсы и временные зоны меняются и написать статичные проверки тяжело.

Accessibility

Решение, которое позволило при помощи UI Automation работать с разными языками, называется Accessibility. Изначально оно предназначено для улучшения работы с интерфейсом людей с ограниченными возможностями, но с пятой версии iOS появилась возможность каждой вьюшке назначить accessibilityIdentifier, который позволяет в процессе тестирования найти нужный компонент как-то так:

var keypad = UIATarget.localTarget().frontMostApp().elements()["Keypad"];

Получается, что если у вас в приложении хорошо поддержана Accessibility («любое приложение должно его поддерживать!», — сказал перфекционист во мне и вздохнул), то и тестировать будет несложно. И наоборот, если подготовить приложение для тестов, то до нормальной поддержки Accessibility останется один шаг.

Как написать свои тесты?

  1. Попробуйте понять, нужны ли они вам. Если вы делаете простое приложение, делали такое уже двадцать раз, или создаёте прототип — тесты помешают. Также плохая практика писать мобильные UI-тесты на начальном этапе разработки, когда каждый день что-то меняется (и, порой, существенно).

  2. Дождитесь, когда устаканится та функциональность, которую хочется оттестировать. Пишете тесты на API, дождитесь, пока он появится на сервере. Впрочем, на API часто есть более чёткое ТЗ, и тесты можно писать заранее, про это смотрите TDD. Проведите традиционное тестирование перед тем, как писать автотесты.

  3. Сфорулируйте правила, по которым должна работать функциональность и метрики, которые нужно проверять. Это, скорее всего, поможет создать специальные методы для проверки, которые помогут в разработке тестов.

  4. Создайте инфраструктуру для тестов. Тесты хороши, когда их много и они маленькие. Если, чтобы создать тест, вам приходится писать даже 5-10 строк, то вы никогда не напишете тысячу тестов, вам просто надоест кодить одно и то же. Сделайте так, чтобы тест создавался одной строкой. Можно вынести конфигурацию в отдельный файл, можно создать макрос или метод.

  5. Иногда полезно сгенерировать тесты. Если вы понимаете, что вот сейчас приложение работает верно, зафиксируйте это состояние, сгенерите тесты прямо самим приложением. В будущем, если что-то поменяется, вы это тут же увидите.

  6. Тесты — это не волшебная палочка, которая «работает». Их нужно поддерживать в рабочем состоянии, они тоже ломаются, в них тоже бывают ошибки. Если вы решили создать тесты, заложите в работу и время на их поддержку и развитие.

Что дальше?

За рамками пока остались полуавтоматические тесты (когда скриншоты смотрятся и контролируются человеком) и тесты на скорость работы интерфейса, с которой начинаются проблемы при добавлении новых единиц. Про это — в следующий раз.

А у вас какой опыт в тестировании приложений для iPhone и iPad?