Привет! Сегодня я хочу рассказать, как мы построили с нуля гибкую и расширяемую систему для выполнения автотестов на Android-смартфонах. Сейчас у нас используется около 60 устройств для регрессионного тестирования мобильного приложения Почты Mail.Ru. В среднем они тестируют около 20 сборок приложения ежедневно. Для каждой сборки выполняется около 600 UI-тестов и более 3500 unit-тестов.
Автотесты доступны круглосуточно — они экономят очень много времени тестировщиков и позволяют нам выпускать качественное приложение. Без них мы бы тестировали каждую сборку 36 часов (с учетом ожидания) или примерно 13 часов без ожидания. Вместе со сборкой, актуализацией переводов, при рабочей загрузке агентов с автотестами тестирование в среднем занимает 1.5 часа, что ежедневно позволяет нам экономить недели работы тестировщиков.
Мы рассмотрим, как всё делать с самого начала тем, кто занимается написанием автотестов, а не инфраструктурой: начиная от покупки телефона, его перепрошивки и заканчивая созданием docker-контейнеров, внутри которых будет доступен телефон для автотестов.
Какие телефоны выбрать для автотестов на Андроиде?
Когда Android только-только становился популярным, у разработчиков тестов был выбор из двух зол: покупать дорогой зоопарк телефонов либо работать с медленными и глючными виртуалками. Сегодня всё несколько проще, на рынке появились дешёвые аппараты «эконом»-класса, а виртуальный Андроид обзавёлся образом для x86 и HAXM. Однако выбор всё ещё остался, многие предпочитают для автотестов виртуальные машины, но телефоны уже стали вполне доступной опцией даже для скромного бюджета на автотестирование. Так как же выбрать телефон для регрессионных автотестов и какое оборудование ещё нужно, чтобы всё вместе оно могло работать 24/7?
Рынок телефонов очень большой — глаза разбегаются. Какие же критерии выставить при выборе телефона? У меня после череды проб и ошибок вышел такой список (цену на аппарат опускаю, с ней, надеюсь, всё понятно):
- Есть возможность получить права root.
- Есть возможность анлока boot-раздела телефона.
- На телефоне стоит версия Андроида, максимально близкая к стоковой, или подобные можно установить (чтобы не пришлось лопатить кучу вариантов теста под разные интерфейсы).
- Оперативная память на телефоне желательно должна быть размером от 1 Гб (можно работать и на меньшей, но, даже если тесты написаны стабильно, различные проверки отображения «тяжёлых» объектов на телефоне с низкой оперативной памятью окажутся долгими).
- Совсем здорово, если у телефона будет долгий саппорт от производителя/пользователей, тогда у нас остаётся шанс продлить ему жизнь новыми версиями ОС.
Основная часть критериев достаточно прозрачна. Если окажется, что на телефоне что-то работает не так, то мы должны хотя бы иметь шанс заставить это работать сами. :) К сожалению, большая часть критериев — это не те вещи, о которых можно спросить продавца при покупке, поэтому в первую очередь наш путь лежит на forum.xda-developers.com и 4pda.ru/forum, где о рыночной модели можно узнать все подробности. Плюс ко всем перечисленным критериям, если модель уже долго продаётся, обращайте на форумах внимание на отзывы о браке и времени ресурса памяти и батареек, без них ваше устройство превратится в тыкву. Проблемы экрана, кнопок, корпуса, динамиков и прочего, что обычно интересует пользователя, вас, как правило, пугать не должны: телефон прекрасно будет работать с браком этих элементов, хотя всё зависит от специфики проекта.
После выбора модели телефона лучше всего сначала заказать одну-две штуки на предварительные тесты, проверить, что операционная система не готовит никаких сюрпризов, все написанные тесты на них корректно проходят и что железо соответствует заявленным производителем характеристикам. В моей практике самые жестокие проколы при покупке были следующие:
1. Модель телефона имеет кучу региональных подвидов, при этом только на части из них можно получить рут или разлочить бутлоадер. Мало того что наткнуться на российский сертифицированный телефон в неофициальном магазине сложно — серые и белые телефоны выглядят одинаково, — так ещё и многие продавцы или их поставщики перепрописывают названия моделей, характеристики железа, регионы и даже версии операционных систем. Вполне возможен случай, когда внутри «Настроек» в Андроиде вы видите одну модель, внутри бутлоадера другую, а в шелле, когда набираете getprop и получаете айдишники, — третью. Просто телефон прошёл пару рук и пару регионов до вас. Сначала его хозяином был пользователь Веризона из Южной Дакоты, потом тот сдал его, в refurbished-состоянии аппарат как-то попал торговцу в Тель-Авиве, который его криво перепрошил на их версию операционной системы, а через ещё какое-то время телефон перекупил продавец в Москве, который уже стал продавать его как новый. Вам привозит его курьер, вы берёте в руки своё новое восьмиядерное перепрошиваемое российское устройство, не подозревая, что это шестиядерный залоченный региональный эксклюзив для контрактных пользователей оператора сотовой связи из США.
Элементы коробки и телефона с «современной» начинкой и высокой ценой, который по внутренним характеристикам оказался перепрошитой младшей моделью от AT&T
2. Один и тот же серийный номер. Здесь определить проблему попроще, но, к сожалению, даже официальные продавцы этим страдают, отсутствие серийного номера — это напасть многих бюджетных девайсов. Если при работе ваших автотестов используется adb, а к машине подключено несколько устройств, нужно либо переписывать код adb (так он увидит только один девайс), либо, если покупка совершалась по критериям выше, вписывать уникальный серийник самому.
Типичные значения серийников у бюджетных телефонов
3. Псевдослучайный MAC-адрес у Wi-Fi-модуля после перезагрузки телефона. Это была довольно серьёзная проблема, потому что мы узнали о ней, когда я убедился, что «всё хорошо», телефон нам подходит, и мы заказали 20 штук точно таких же. :) В процессе работы автотестов телефоны иногда перезагружаются, через какое-то время тесты начали падать из-за отсутствия доступа к сети по Wi-Fi, хотя всё при этом выглядело нормально: соединение с сетью было и после включения/выключения Wi-Fi-модуля всё работало корректно. Просто после ребутов в какой-то момент у пары телефонов оказывался одинаковый MAC, Wi-Fi-точка доступа же пускала только последний присоединившийся. На тех телефонах, где в итоге генерился MAC-адрес, я, к сожалению, не нашёл, пришлось в загрузочном разделе поместить скрипт, который устанавливал его насильно на уникальный.
Телефон демонстрирует чудеса spoofing’а из коробки
Тем не менее, если соблюдать при выборе телефона перечисленные выше критерии, эти проблемы не должны быть фатальными — всё это можно исправить руками и заставить телефон работать как нужно.
Кроме телефонов, для запуска автотестов понадобится сам компьютер и USB-хабы, тут тоже есть несколько нюансов. Постоянно работающим телефонам нужно хорошее питание (минимум 0,5 А на устройство, лучше больше), многие хабы на рынке идут со слабыми адаптерами и никак не рассчитаны на то, что в каждый порт будет подключён постоянно работающий телефон. С планшетами ещё сложнее, девятидюймовые планшеты при постоянной работе разряжаются, экран слишком большой, приходится выбирать из семидюймовых. Из практики у нас вышло, что на адаптер в 4 А можно подключить 6–7 телефонов (в зависимости от их загрузки работой), т. е. большая часть многопортовых хабов с характеристиками типа «адаптер на 3 А, 20 USB-портов», мягко говоря, бесполезны. Самые крутые — серверные решения, но цена у них зашкаливает, так что ограничимся пользовательским рынком. Чтобы телефоны не разряжались, стоит брать хабы на четыре порта с питанием в 3 А, либо хабы на шесть портов с питанием на 4 А. Если есть хабы с хорошим питанием, но с большим количеством портов, — часть портов можно просто не использовать.
Готовим телефон к работе
Давайте для примера возьмём одну модель телефона, решим одну из проблем его операционной системы, а дальше попробуем собрать эти устройства в примитивный тестовый стенд для автотестов. Телефон сам по себе дешёвый и хороший, но с некоторыми недостатками (описанными выше). В частности, у этих телефонов одинаковый iSerial, adb видит только одно устройство. :( Совсем везде на телефоне его менять не будем, но сделаем так, чтобы adb отдельные телефоны отличал.
Для этого нам нужно будет перепрошить у телефона boot-раздел и установить на устройстве кастомный раздел восстановления — так вы сможете уберечься от неудачных экспериментов. У наших телефонов стоит МТ6580, т. е. процессор фирмы Mediatek, значит, для перепрошивки можно использовать SP Flash Tool. Ещё нужны готовый образ recovery.img и scatter-файл устройства. Почти для всех устройств их можно найти в интернете, на тех же самых ресурсах XDA и 4PDA, но при желании recovery можно перекомпилировать для своего устройства, взяв за основу TWRP, а scatter-файл создать самому. В любом случае берём наши готовые файлы и перепрошиваем:
После установки раздела восстановления сохраните через него бэкап boot-раздела и переместите его к себе на машину, обычно в этом разделе располагаются конфигурационные файлы ОС. Чтобы захардкодить свой iSerial, нужно распаковать образ загрузочного раздела телефона, это можно сделать с помощью Android Image Kitchen. Запускаем unpackimg.sh и получаем распакованный образ в папке ramdisk:
Здесь много init-файлов, в которых указываются различные переменные, в том числе и серийный номер.
Находим файл, где устанавливается серийный номер ${ro.serialno}
, и заменяем его на свой номер, например 999222333019:
find ramdisk/ -maxdepth 1 -name "init.mt*" -exec sed -i 's/${ro.serialno}/999222333019/g' {} +
Запаковываем образ обратно с помощью repackimg.sh, перекидываем его на телефон и устанавливаем с помощью кастомного рекавери. Теперь adb будет отличать устройства, нам остаётся включить режим разработчика на телефоне и разрешить debug в меню разработчика. Любые подобные проблемы можно решать точно таким же путём, практически всё в телефоне можно перепрошить или поправить, если этого потребуют задачи тестирования.
Настройка тестовой машины
В качестве хоста нашего стенда будем использовать обычный десктоп с установленной на него Ubuntu. Телефоны затем можно будет разделить на отдельные виртуальные машины, использовать все вместе или, как это делаю я, разделить их в отдельные docker-контейнеры.
При заказе/сборке машины, к которой будут подключены телефоны, есть специфика. Кроме стандартных HDD/RAM/CPU, нужно обратить внимание на количество USB-контроллеров на материнской плате и поддерживаемый протокол USB. Телефоны, работающие на USB 3.0 (xHCI), могут существенно ограничить максимальное количество устройств на машине (обычно 8 на контроллер, в итоге 16 устройств на машину с двумя контроллерами), поэтому стоит проверить, есть ли возможность его отключить и использовать только EHCI. Такие опции есть в биосе или ОС, лучше всего насильно отключить xHCI в биосе, если вам не нужны высокоскоростные устройства.
Создаём контейнер для телефона
Если нужно, чтобы слейв / агент системы интеграции работал с отдельным телефоном, то их следует как-то разделить. У нас агенты запускаются в отдельных docker-контейнерах, каждому из которых доступно по одному устройству, — так мы можем распределять задачи в CI на отдельные устройства, а точнее на их возможности (например, контейнер с планшетом может выполнять тесты, для которых требуется широкая диагональ экрана, а контейнер с телефоном — тесты, для которых нужна возможность принимать SMS-сообщения). Пример установки и настройки системы на Ubuntu описан далее. Устанавливаем сам Docker:
sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
echo 'deb https://apt.dockerproject.org/repo <версия ubuntu> main' >> /etc/apt/sources.list.d/docker.list
sudo apt-get update
sudo apt-get install docker-engine
В качестве сторадж-драйвера будем использовать overlayFS (работает быстрее дефолтного):
echo 'DOCKER_OPTS="-s overlay"' >> /etc/default/docker
Создаем dockerfile, из которого будем делать образы. Добавим в него Android SDK:
FROM ubuntu:trusty
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update -y &&
apt-get install -y software-properties-common &&
add-apt-repository ppa:webupd8team/java -y &&
apt-get update -y &&
echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections &&
apt-get install -y oracle-java8-installer &&
apt-get remove software-properties-common -y &&
apt-get autoremove -y &&
apt-get clean
ENV JAVA_HOME /usr/lib/jvm/java-8-oracle
ENV ANT_VERSION 1.9.4
RUN cd &&
wget -q http://archive.apache.org/dist/ant/binaries/apache-ant-${ANT_VERSION}-bin.tar.gz && tar -xzf apache-ant-${ANT_VERSION}-bin.tar.gz && mv apache-ant-${ANT_VERSION} /opt/ant && rm apache-ant-${ANT_VERSION}-bin.tar.gz
ENV ANT_HOME /opt/ant
ENV PATH ${PATH}:/opt/ant/bin
ENV ANDROID_SDK_VERSION r24.4.1
ENV ANDROID_BUILD_TOOLS_VERSION 23.0.3
RUN dpkg --add-architecture i386 &&
apt-get update -y &&
apt-get install -y libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 &&
rm -rf /var/lib/apt/lists/* &&
apt-get autoremove -y &&
apt-get clean
ENV ANDROID_SDK_FILENAME android-sdk_${ANDROID_SDK_VERSION}-linux.tgz
ENV ANDROID_SDK_URL http://dl.google.com/android/${ANDROID_SDK_FILENAME}
ENV ANDROID_API_LEVELS android-15,android-16,android-17,android-18,android-19,android-20,android-21,android-22,android-23
ENV ANDROID_HOME /opt/android-sdk-linux
ENV PATH ${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools
RUN cd /opt &&
wget -q ${ANDROID_SDK_URL} &&
tar -xzf ${ANDROID_SDK_FILENAME} &&
rm ${ANDROID_SDK_FILENAME} &&
echo y | android update sdk --no-ui -a --filter tools,platform-tools,${ANDROID_API_LEVELS},build-tools-${ANDROID_BUILD_TOOLS_VERSION}
###Добавим файл системы интеграции, это может быть слейв дженкинса, агент Bamboo и т. п., в зависимости от того, с чем вы работаете
ADD moyagent.sh /agentCI/
В докерфайл также можно добавить все необходимые библиотеки и файлы, которыми будет пользоваться агент системы интеграции. Соберём dockerfile:
docker build .
Теперь у нас есть образ с Android SDK, осталось сделать так, чтобы он видел только одно устройство. Для этого будем цеплять его через симлинк c помощью udev:
echo ‘"SUBSYSTEM=="usb", ATTRS{serial}=="$DEVICE_SERIAL", SYMLINK+="androidDevice1"’ >> /etc/udev/rules.d/90-usb-symlink-phones.rules
Вместо $DEVICE_SERIAL
вписываем наш свежепрошитый серийник. Перезапускаем определение правил устройств:
udevadm control --reload
udevadm trigger
Теперь в пути /dev/androidDevice1 у нас будет симлинк на устройство, осталось передать его в контейнер при запуске:
docker run -i -t --rm --device=/dev/androidDevice1:/dev/bus/usb/001/1 android-docker-image:latest adb devices
Как только убедимся, что телефон из контейнера виден только один, можно запускать расположенный в контейнере агент системы интеграции:
docker run -i -t --rm --device= /dev/androidDevice1:/dev/bus/usb/001/1 android-docker-image:latest /bin/sh /agentCI/moyagent.sh
Кстати, ключ --device
стал работать с симлинками относительно недавно (master-ветка на Гитхабе), до этого приходилось генерить из симлинка realpath c помощью скрипта и уже его передавать Докеру, так что если у вас не выходит подключение устройства, то добавьте в udev в параметр RUN+=
такой скрипт:
realpath /dev/androidDevice1 | xargs -I linkpath link linkpath /dev/bus/usb/010/1
После этого в старых версиях Docker добавить телефон можно так:
docker run --privileged -v /dev/bus/usb/010/:/dev/bus/usb/100/ -i -t android-docker-image:latest adb devices
Всё, можно подключать свой слейв к системе интеграции и работать с ним.
Заключение
Физические мобильные устройства в системе интеграции рано или поздно появляются у любого более-менее крупного проекта на Андроиде — неизбежно возникают необходимость покрытия ошибок, нестандартные тестовые случаи или просто фичи, которые требуют реального устройства. Кроме всего этого, устройства не используют ресурсы ваших серверов, так как процессоры и память у них свои, а хост для телефонов не должен быть супермощным, «домашний» десктоп со всем этим вполне справится. Соизмеряйте плюсы и минусы, считайте, что выгоднее, — наверняка в вашей системе автоматизированного тестирования есть место реальным устройствам. Желаю вам поменьше багов и побольше тестового покрытия. :)
Автор: Mail.Ru Group