Простая автоматизация нагрузочного UI тестирования для Android QA инженера

Здравствуйте, друзья,
Пост посвящен тому, как оттестировать множественные переходы между разными приложениями с выполнением некоторых действий внутри приложений.
Казалось бы тривиальная задача 🙂 На самом деле, на сегодняшний день существует только одно ее простое решение (и одно сложное через MonkeyRunner). Именно этим решением я и хочу с вами поделиться 🙂
Для начала рассмотрим, какие подходы в целом существуют для Android UI тестирования, а так же их за и против:

1. Jayway Robotium Solo (http://code.google.com/p/robotium/) – open source библиотека для black box тестирования. За: идеальное black box тестирование с огромными возможностями assert-ов и взаимодействия с приложением. Против: невозможно протестировать user-like переход между приложениями, т.к. библиотека не может выйти за рамки target package – ограничено тестируемым приложением.

2. Monkey (http://developer.android.com/guide/developing/tools/monkey.html). Обезъяныч просто посылает большое число различных команд приложению (тачи, свайпы и так далее) За: можно нагрузить приложение, для проверки его стабильности, в случае, когда нужно протестировать monkey-like взаимодействие с приложением Против: невозможно создавать сценарии или воспроизводить баги по результатам работы обезьянки. Ограничено тестируемым приложением.

3. Android MonkeyRunner (http://developer.android.com/guide/developing/tools/monkeyrunner_concepts.html). Скажу только, что это очень серьезная и крутая тулза, которая не подходит под заголовок “Простая автоматизация”. В целом, MonkeyRunner является продолжением данного поста и использует Python, что может оказаться подводным камнем для многих. Опишу в будущем отдельно это утилиту.

4. Использовать shell скрипт, на подобие того, как было описано в посте: http://dev.by/blog/54818. За: полная свобода передвижения по девайсу/эмулятору и полная свобода взаимодействия с устройством. Можно создавать достаточно сложные сценарии. Против: тяжело обстоит ситуация с assert-aми. Я смог найти только возможность делать assert для проверки, какая активити в фокусе, заблокировано ли устройство, открыта ли экранная клавиатура и так далее (проверка того, что можно получить из adb shell dumpsys). Требует определенной усидчивости и настойчивости.

5. Для справки: Web UI тестирование для Android можно осуществить с помощью Android Selenium driver: http://code.google.com/p/selenium/wiki/AndroidDriver
Нельзя сказать, что какой-то из этих подходов супер, а другой полная фигня. Все они нацелены на свои задачи, как инструменты: пила, молоток, кувалда и напильник 🙂
Рассмотрим более подробно подход через shell скрипты (так, как это “простая автоматизация”, то мы исключим assert-ы фокусированных активити), для этого нам нужно освоится с командами:

  1. adb shell getevent (Дампит происходящие события физического уровня на девайсе)
  2. adb shell sendevent (Позволяет воспроизводить события физического уровня на девайсе)
  3. adb shell dumpsys window (Дампит текущую информацию сервиса отрисовки дисплея, содержит имя текущей активити в фокусе)
  4. sleep n, где n – интервал ожидания от 0 до дофига в секундах, причем если нужно подождать в милисекундах, то необходимо использовать значения с плавающей точкой: sleep 0.1 – подождет 100 милисекунд.

Чтобы написать сценарий для девайса, нужно знать его события для воспроизведения прикосновения к дисплею, для этого мы используем getevent:
~$ adb shell getevent
Дает нам следующий вывод для Андроид 2.2 эмулятора (Будем использовать Android 2.2 WVGA 800):
add device 1: /dev/input/event0
name: "qwerty2"
could not get driver version for /dev/input/mouse0, Not a typewriter
could not get driver version for /dev/input/mice, Not a typewriter

Делаем быстрый клик мышью на иконке меню, чтобы открыть список приложений:

/dev/input/event0: 0003 0000 0000010f /dev/input/event0: 0003 0001 000001cf /dev/input/event0: 0001 014a 00000001 /dev/input/event0: 0000 0000 00000000 /dev/input/event0: 0001 014a 00000000 /dev/input/event0: 0000 0000 00000000

Поздравляю, мы получили дамп простого тача дисплея 🙂 Теперь если мы переведем все числа в десятичную систему:

/dev/input/event0: 3 0 250 # x /dev/input/event0: 3 1 770 # y /dev/input/event0: 1 330 1 # touch down /dev/input/event0: 0 0 0 # event separator /dev/input/event0: 1 330 0 # touch up /dev/input/event0: 0 0 0 # click is finished

Теперь добавим вначале каждой строки adb shell sendevent и уберем все двоеточия:

adb shell sendevent /dev/input/event0 3 0 250 # x adb shell sendevent /dev/input/event0 3 1 770 # y adb shell sendevent /dev/input/event0 1 330 1 # touch down adb shell sendevent /dev/input/event0 0 0 0 # event separator adb shell sendevent /dev/input/event0 1 330 0 # touch up adb shell sendevent /dev/input/event0 0 0 0 # click is finished

Записываем данную последовательность в файл (click.sh)и выполняем (не забудте сделать chmod 777 click.sh, чтобы разрешить выполнение). В моем случае, я одной и той же командой могу заходить в меню приложений и выходить из него. Теперь параметризируем этот файл:

adb shell sendevent /dev/input/event0 3 0 $1 # x adb shell sendevent /dev/input/event0 3 1 $2 # y adb shell sendevent /dev/input/event0 1 330 1 # touch down adb shell sendevent /dev/input/event0 0 0 0 # event separator adb shell sendevent /dev/input/event0 1 330 0 # touch up adb shell sendevent /dev/input/event0 0 0 0 # click is finished

Вызов: ~$ ./click.sh 250 770

Таким способом мы можем написать параметризированный скрипт для воспроизведения клика по экрану для любого девайса/эмулятора.

На заметку: если вам необходимо воспроизвести swipe (например, чтобы разблокировать девайс). То сделать это проще простого, нужно отправить устройству много touch down событий с изменяющимеся координатами x или y, в зависимости от swipe, разделим click.sh на click_down.sh и click_up.sh:

click_down.sh: adb shell sendevent /dev/input/event0 3 0 $1 # x adb shell sendevent /dev/input/event0 3 1 $2 # y adb shell sendevent /dev/input/event0 1 330 1 # touch down adb shell sendevent /dev/input/event0 0 0 0 # event separator

click_up.sh: adb shell sendevent /dev/input/event0 1 330 0 # touch up adb shell sendevent /dev/input/event0 0 0 0 # click is finished

На моем эмуляторе точка, с которой нужно начинать разблокировку экрана: 83:608. Создадим еще один строительный блок для наших шелл тестов – unlock_screen.sh:

./click_down.sh 83 608 ./click_down.sh 150 608 ./click_down.sh 250 608 ./click_down.sh 350 608 ./click_down.sh 450 608 ./click_up.sh

Теперь разберемся, как нажимать кнопку Power и Back: 1. Записываем последовательность событий с помощью adb shell getevent: Power:

/dev/input/event0: 0001 0074 00000001 /dev/input/event0: 0001 0074 00000000

Back:

/dev/input/event0: 0001 009e 00000001 /dev/input/event0: 0001 009e 00000000

2. Декодируем:

Power:

adb shell sendevent /dev/input/event0 1 116 1 # Pressed adb shell sendevent /dev/input/event0 1 116 0 # Realeased

Back:

adb shell sendevent /dev/input/event0 1 158 1 # Pressed adb shell sendevent /dev/input/event0 1 158 0 # Realeased

Аналогично можно записать команду для любой кнопки эмулятора. Отличие от физического устройства будет в том, что на физическом устройстве будет другой интерфейс (у эмулятора все на одном интерфейсе), например, тач скрин event2, а кнопки на event1.

Сохраняем наш строительный кирпичик в файл click_power.sh и click_back.sh соответственно (не забываем делать chmod 777).

Теперь у нас есть следующие блоки будущего тестового сценария:

  1. click_down.sh x y
  2. click_up.sh
  3. click.sh x y
  4. unlock_screen.sh
  5. click_power.sh
  6. click_back.sh

Напишем простой сценарий, который покрывает следующий user case:

  1. Нажимаем кнопку power, чтобы включить экран устройства
  2. Разблокируем экран
  3. Заходим в меню приложений
  4. Открываем по очереди первых пять приложений (выходим из приложений кнопкой Back, палагаем, что приложение безусловно выйдет)
  5. Блокируем экран кнопкой power

Сценарий будет выглядеть следующим образом (запишем все в файл test_scenario_1.sh):

./click_power.sh
./unlock_screen.sh
sleep 2
./click.sh 250 770 # из примера выше, на Андроид 2.2 эмуляторе попадает как раз в иконку с приложениями
sleep 2
./click.sh 67 123 # кликаем первое приложение
sleep 5
./click_back.sh
sleep 1
./click.sh 174 123 # кликаем второе приложение
sleep 5
./click_back.sh
sleep 1
./click.sh 422 123 # кликаем третье приложение
sleep 5
./click_back.sh
sleep 1
./click.sh 250 770 # из примера выше, на Андроид 2.2 эмуляторе попадает как раз в иконку с приложениями
sleep 2
./click_power.sh

Оптимизация: в конец каждого скрипта добавить параметр sleep, чтобы не писать везде sleep X. Тогда сценарий будет выглядеть совсем чудно:

./click_power.sh 1 ./unlock_screen.sh 2 ./click.sh 250 770 2 ./click.sh 67 123 5 ./click_back.sh 1 ./click.sh 174 123 5 ./click_back.sh 1 ./click.sh 422 123 5 ./click_back.sh 1 ./click.sh 250 770 2 ./click_power.sh

Путем подобных манипуляций можно строить достаточно сложные стресс тесты. Хочу заметить, что в промышленных масштабах, данный подход выявил большее число багов (на протяжении 2х лет), чем любой другой 🙂 Что примечательно, данный подход очень легко поддерживать, в отличие от тех, которые зависят от исходного кода продукта.

Чтобы добавить assert-ы на активити, которая в фокусе, необходимо изучить вывод

~$ adb shell dumpsys window

Дамп данного сервиса, даст нам важную строку:

mCurrentFocus=Window{45062ac0 com.android.launcher/com.android.launcher2.Launcher paused=false}

Сообщит нам о том, какая сейчас активити в фокусе 🙂 Чтобы получать только нужную нам строку, можем добавить grep:

~$ adb shell dumpsys window | grep mCurrentFocus

Для получения состояния дисплея, необходимо изучить следующие строки:

KeyWaiter state: mLastWin=null mLastBinder=null mFinished=true mGotFirstWindow=true mEventDispatching=true mTimeToSwitch=0mEventDispatching=true – экран не заблокирован. mEventDispatching=false – экран заблокирован.

На заметку: данный шелл сценарий, если убрать везде adb shell можно поместить на девайс и вызвать программно из Java кода 😀 Я думаю не стоит даже рассказывать, какие гаризонты сразу покоряются ^_^

Желаю вам успехов в повышение качества ваших продуктов, но теперь с меньшим количеством ежедневной рутины (:

С уважением,
Егор