Обложка: Автотесты на Espresso: первые вопросы и способы их решений

Автотесты на Espresso: первые вопросы и способы их решений

Ангелина
Ангелина

Noveo Senior Test Engineer

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

Вопрос 0, с которым следует разобраться, — это принять решение, какой же фреймворк для автоматизации выбрать 🙂

При поиске фреймворка для автоматизации мобильного приложения стоит выбор: нативные инструменты (Espresso для Android / XCUITest для iOS) или кроссплатформенные решения (Appium). Я тестирую мобильное приложение, и нашей команде тоже пришлось искать ответ на вопрос: «А что же лучше?». Взвесив все «за» и «против», мы приняли решение использовать нативные инструменты.

В этой статье я собрала несколько вопросов, с которыми столкнулась при написании первых автотестов на Espresso — нативном фреймворке от Google для автоматизации приложений под Android, и предложила решения, которые внедряла по ходу своей работы.

Вопрос 1. Что же нужно писать в @Rule и почему примеры из интернета не всегда работают?

Если вы погуглите, то в любом источнике найдёте вот такой пример правила для исполнения тестов в Espresso (Java):

@Rule
public ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<>(MainActivity.class);

Казалось бы, копировать-вставить — всё готово и должно отлично работать. Делала ли я так? Конечно, да. Заработало ли оно? Конечно, нет. Но почему? Тесты должны запускаться в MainActivity? Да. Есть в приложении MainActivity (прим.: подробнее про Activity можно почитать здесь: https://developer.android.com/reference/android/app/Activity)? Тоже да.

Причина того, почему это правило не срабатывает, может состоять в том, что тестируемое приложение стартует не с MainActivity. Например, если launcher, то есть активити, с которой запускается приложение, — SplashActivity (понять это можно по splash-скрину, который показывается при запуске приложения), то именно её нужно указывать в правиле. Чтобы узнать наверняка, с какой активити стартует приложение, нужно найти файл app/src/main/AndroidManifest.xml, найти список всех активити и посмотреть, какая из них имеет категорию android.intent.category.LAUNCHER.

То есть в нашем примере корректное правило выглядит так:

@Rule
public ActivityTestRule<SplashActivity> activityTestRule = new ActivityTestRule<>(SplashActivity.class);

Вопрос 2. Где брать id элементов для автотестов на Espresso?

Ответ, казалось бы, прост: в xml-файлах из папки layout. Но если ваше приложение включает в себя более 5-6 экранов, то придётся уделить немало времени на то, чтобы перебрать все файлы в поисках id нужного элемента. К счастью, в Android Studio есть инструмент, который позволяет быстро находить максимум необходимых данных дня написания автотеста на Espresso: Tools -> Layout Inspector (https://developer.android.com/studio/debug/layout-inspector).

Layout Inspector позволяет просматривать иерархию элементов и все характеристики каждого элемента на выбранном экране. Экраны в Layout inspector меняются в режиме реального времени: как только вы на эмуляторе перешли на другой экран, Layout inspector обновится и покажет данные для открывшегося экрана. Однако минус последней версии Layout inspector-а состоит в том, что он не позволяет копировать данные из панели характеристик (например, скопировать текст, который содержится в нужном для теста элементе).

Если для вас это критично, то можно использовать предыдущую версию Layout Inspector-а — Legacy Layout Inspector. Он позволяет копировать характеристики элементов, но не обновляет экраны в режиме реального времени, необходимо открывать отдельное окно Inspector-а для каждого экрана. Какую версию Inspector-а лучше использовать, решать, конечно же, вам, но в любом случае этот инструмент — маст хэв при написании автотестов на Espresso.

Вопрос 3. Два элемента на экране имеют одинаковый id, что делать?

В примерах тестов на Espresso можно найти очень простые варианты кода:
onView(withId(R.id.id_example)).perform(click()). Но что, если, допустим, два поля ввода на одном экране имеют одинаковые id?

В ряде случаев одним matcher-ом (matcher — условие, по которому ищется элемент на экране; в веб-автоматизации — локатор) сложно максимально точно конкретизировать, какой именно элемент нужен в конкретном шаге. С другой стороны, добавлять matcher-ы в огромном количестве тоже не стоит. В случае с полями ввода было достаточно добавить всего один дополнительный matcher для корректного определения каждого элемента:

onView(
allOf(
withId(R.id.textField),
isDescendantOfA(withId(R.id.parentElement))
)
).perform(some_action)

Matcher-ов может быть и 3, и 4, если их добавление в тест действительно оправдано. Главное — задавать себе вопрос: сколько matcher-ов необходимо и достаточно, чтобы со стопроцентной точностью найти именно этот элемент?

Вопрос 4. Какие есть методы ожидания?

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

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

В любом источнике, где говорится о методах ожидания в любых автотестах, не только для мобильных приложений, чётко и ясно написано: НЕ ИСПОЛЬЗОВАТЬ В ТЕСТАХ Thread.sleep(). Они делают тесты менее стабильными, плюс добавление Thread.sleep-ов искусственно удлиняет время прохождения теста.

В Espresso в качестве способов ожидания предлагается использование Idling resources. Однако есть один нюанс: в вашем приложении уже должны быть добавлены Idling Resources, чтобы вы могли использовать их в своих тестах. Но на момент, когда вы начинаете писать автотесты, в вашем приложении этого может и не быть (например, в случае, если изначально написание автотестов командой не планировалось).

Когда мы столкнулись с этой проблемой, то решили поступить таким образом: пока команда Android добавляла Idling Resources для автотестов (да, здесь необходима помощь и поддержка команды разработки), я писала автотесты с Thread.sleep(), чтобы не терять время и добавлять основную логику проверок, хоть и без правильных и рекомендованных методов ожидания. Я не хочу сказать, что Thread.sleep() — это суперспособ, однако он может стать вашим спасением на тот случай, если на добавление правильного решения требуются время и ресурсы команды разработки, а приостанавливать написание автотестов из-за этого не хочется.

Вопрос 5. Почему scrollTo не работает?

Как вариант, потому что в вашем приложении используется не просто ScrollView и не HorizontalScrollView, а NestedScrollView. Ни scrollTo, ни остальные методы для поиска элемента в RecyclerView в Espresso не работают с NestedScrollView, если элемент, который нужен вам для теста, не является видимым в момент обращения к RecyclerView.

Пока что я в поиске решения этой проблемы. Для нескольких кейсов в моём приложении мне, как альтернатива, подходит swipeUp(), но он точно не поможет, если у вас RecyclerView с 50 элементами и обратиться нужно к 49-му.

Вопрос 6. Я только начинаю писать автотесты на Espresso и понятия не имею, как описывать PageObjectModel.

Ответ: пробуйте писать так, как можете и как понимаете. Например, просто пишите набор команд, необходимых для выполнения теста. Напишите так 5-10 тестов на одну функциональность. За это время у вас появится понимание, какие части кода можно переиспользовать, чтобы избежать дублирования, и как должны выглядеть ваши PageObjectModel.

Мои первые тесты выглядели примерно вот так (Kotlin):

onView(
withId(R.id.continueButton)
).perform(click())

onView(
withId(R.id.enterEmail)
).perform(replaceText(“email@example.com”))

onView(
withId(R.id.enterPassword)
).perform(replaceText(“password”))

onView(
withId(R.id.loginButton)
).perform(click())

И только потом они превратились в (Kotlin):

POM:
class POM {
fun tapButton(id: Int) {
    onView(
withId(id)
).perform(click())
}

fun enterData(id: Int, text: String) {
    onView(
withId(id)
).perform(replaceText(text))
}
}

Test:
Class test {
private val idContinue = R.id.continueButton
private val idEnterEmail = R.id.enterEmail
private val email = “email@example.com”
private val idEnterPassword = R.id.enterPassword
private val password = “password”
private val idLogin = R.id.loginButton

@Test
fun test() {
with(POM) {
tapButton(idContinue)
enterData(idEnterEmail, email)
enterData(idEnterPassword, password)
tapButton(idLogin)
}
}
}

Решения и подходы, предложенные мной, разумеется, не единственные. Однако я надеюсь, что они помогут начинающим автоматизаторам сэкономить часы ресёрча и поверить, что писать автотесты и правда просто! Главное — начать 🙂

С какими проблемами в написании автотестов вы сталкивались?