
Паттерн Page Object появился в тестировании web и очень хорошо себя там зарекомендовал. Когда я начал автоматизировать тесты для android приложения, то первым делом подумал про него. Поискал информацию в сети, поспрашивал коллег и, в принципе, не нашел доводов не попробовать. Предлагаю посмотреть, что из этого вышло.
Классический Page Object подразумевает два уровня абстракции: элементы страницы и тесты. Я выделяю еще один — бизнес-логика. Отмечу, что то, как вы построите ваш фреймворк, будет очень сильно влиять на простоту написания тестов в будущем, а так же на их поддержку. Я стараюсь делать так, чтобы код теста выглядел как будто это обычный тест-кейс, написанный рядовым тестером. Т.е. начинаю с конца:
- Пишу красивый и понятный код тест-кейса,
- Реализую методы из тест-кейса в слое бизнес-логики,
- Описываю элементы, которые нужны для работы теста.
Данный подход хорош тем, что мы не делаем ничего лишнего — фреймворк построен на столько, на сколько это нужно для работы тестов. Можно сказать, что это концепция MVP в тестировании: быстро сделали кусочек, и он уже начал приносить пользу. Если вы сначала пишете тысячи строк, описывая страницы вашего приложения и способы взаимодействия с ними, а месяца через три вылезаете из «норы» для первого клика и осознаете, что надо было сделать все по-другому, то большая часть вашего творения будет обречена навсегда «пылиться» в подвалах git'а… Истинный тестировщик знает, что чем раньше нашел ошибку, тем дешевле ее исправлять. Используйте этот подход во всем — написал тест за пару часов, попробовал, не понравилось — выкинул, извлек урок, поехал дальше.
Итак, входные данные:
- Android приложение для торговли на бирже;
- Java, чтобы работать в одном стэке с разработчиками;
- базовый фреймворк UI Automator;
- нужно написать тест логина в приложение.
Подготовка проекта
Так как я старался максимально интегрироваться в процесс разработчиков, то новый проект создавать не стал. Согласно документации, инструментальные тесты необходимо разместить в папке
src/androidTest/java. В моем случае сборщик уже был настроен, если у вас не так, то почитайте про конфигурацию билда. Также нам нужна среда разработки, Android SDK, Emulator, tools, platform-tools и необходимые платформы для эмулятора. Если вы пользуетесь Android Studio, то все это можно быстро поставить через SDK Manager:
Слой тестов
Как писал выше, будем автоматизировать тест на логин в приложении. Помним, что код должен выглядеть так же, как обычный тест-кейс:
Precondition: запустить приложение.
Step 1: осуществить логин под учетной записью myLogin/myPassword.
Step 2: проверить имя текущего пользователя.
Expected Result: текущий пользователь «Иванов Иван».
Небольшой дисклеймер: согласно лучшим практикам тест дизайна, в предусловии нужно создать/найти учетную запись. Я этот момент опустил для простоты примера.
Наш класс будет выглядеть так:
@RunWith(AndroidJUnit4.class) public class LoginTests { private TestApplication myApp; @Before public void setUp() { myApp = new TestApplication(); } @Test public void testLogin() { myApp.login("myLogin","myPassword"); String currentUser = myApp.getCurrentUserName(); assertEquals("Wrong name", "Иванов Иван", currentUser); } @After public void tearDown() { myApp.close(); } }
Бизнес-логика
В тесте используется класс
TestApplication() и два его метода: login() и getCurrentUserName(). Плюс нужен конструктор класса (в нем запуск приложения) и метод close() Фронт работы ясен:public class TestApplication { private UiDevice device; private PageObject page; public TestApplication() { } public void login(String login, String password) { } public String getCurrentUserName() { return "" } public void close() { } }
У экземпляра нашего класса будет две переменные:
device, принадлежащая классуandroid.support.test.uiautomator.UiDevice— через нее взаимодействуем с нашим устройством;page, классPageObject, который мы будем создавать в следующем разделе.
Начнем с конструктора. В нем создадим экземпляры наших переменных и запустим приложение:
public TestApplication() { // Connect to device device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); device.pressHome(); // Get launch intent String packageName = InstrumentationRegistry.getTargetContext() .getPackageName(); Context context = InstrumentationRegistry.getContext(); Intent intent = context.getPackageManager() .getLaunchIntentForPackage(packageName) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // Stat application context.startActivity(intent); // Get page objects page = new PageObject(device, packageName); }
Пара советов про запуск приложения
Для бОльшего контроля тестируемого приложения можно добавить ожидания запуска лончера (если запускаете эмулятор «на холодную») и самого приложения. Так советуют и в документации.
Замечание: запуск приложения через
android.content.Context.getTargetContext() подходит только в том случае, если ваши тесты в одном проекте с самим приложением. Если отдельно, то необходимо будет запускать через меню.Бизнес-логика теста — это конкретные шаги, которые должен совершить пользователь для получения какого-либо значимого (для пользователя) результата. Осуществление входа под своей УЗ — значимый результат. Шаги: клик на кнопку «Login», ввести имя пользователя в поле «Login», ввести пароль в поле «Password», кликнуть на кнопку «Sign In». Таким образом, наш метод наполняется шагами:
public void login(String login, String password) { page.login().click(); page.loginEntry().setText(login); page.passwordEntry().setText(password); page.signIn().click(); }
Для получения текущего пользователя все проще, просто получаем значение поля:
public String getCurrentUserName() { return page.currentUserName().getText(); } }
А для закрытия приложения просто нажмем на кнопку Home:
public void close() { device.pressHome(); }
Описание элементов страницы
Концепция данного слоя — он должен возвращать готовые к использованию элементы (в нашем контексте это класс
android.support.test.uiautomator.UiObject2). Т. е., потребитель не должен переживать на счет состояния объекта, если он вернулся, то с ним сразу же можно взаимодействовать: кликать, заполнять или считывать текст. Отсюда важное следствие — в этом слое будем реализовывать ожидания:private UiObject2 getUiObject(BySelector selector) { return device.wait(Until.findObject(selector), 10000); }
Метод, определенный выше, будет использоваться публичным интерфейсом нашего класса
PageObject. Пример для поля «Login»:public UiObject2 loginEntry() { return getUiObject(loginEntrySelector()); }
Осталось определить селектор, по которому будем находить поле. Экземпляр
android.support.test.uiautomator.BySelector удобнее всего получить с помощью статических методов класса android.support.test.uiautomator.By. Я договорился с разработкой, что везде, по возможности, будут уникальные resource-id:private BySelector loginEntrySelector() { return By.res(packageName, "login"); }
Инспекцию интерфейса удобно производить в утилите uiautomatorviewer, входящей в пакет tools (устанавливали в разделе Подготовка):

Выделяем нужный элемент и в разделе Node detail смотрим resource-id. Он состоит из названия пакета и, собственно, айдишника. Название пакета мы получили в конструкторе класса
TestApplication, и использовали при создании объекта PageObject.Код полностью:
класс PageObject
public class PageObject { private UiDevice device; private final String packageName; private BySelector loginButtonSelector() { return By.res(packageName, "user_view"); } private BySelector loginEntrySelector() { return By.res(packageName, "login"); } private BySelector passwordEntrySelector() { return By.res(packageName, "password"); } private BySelector signInSelector() { return By.res(packageName, "btnLogin"); } private BySelector userNameSelector() { return By.res(packageName, "user_name"); } public PageObject(UiDevice device, String packageName) { this.device = device; this.packageName = packageName; } public UiObject2 login() { return getUiObject(loginButtonSelector()); } public UiObject2 loginEntry() { return getUiObject(loginEntrySelector()); } public UiObject2 passwordEntry() { return getUiObject(passwordEntrySelector()); } public UiObject2 signIn() { return getUiObject(signInSelector()); } public UiObject2 currentUserName() { return getUiObject(userNameSelector()); } private UiObject2 getUiObject(BySelector selector) { return device.wait(Until.findObject(selector), 10000); } }
Запуск тестов
На этом все, осталось запустить. Для начала подключаемся к устройству или стартуем эмулятор. Проверим подключение командой
adb devices (утилита adb входит в состав platform-tools):List of devices attached emulator-5554 device
Если у вас gradle, то далее делаем
gradlew.bat connectedAndroidTest
и наслаждаемся как «вкалывают роботы, а не человек» (с).
