Kaspresso для самых маленьких
Привет! Меня зовут Элчин, я занимаюсь автоматизацией мобильных приложений в hh.ru и расскажу вам о том, как написать первый тест на Android
. В разработке автотестов мы используем Kotlin
и нативный фреймворк Kaspresso
, о котором я напишу подробней в этой статье.
В рассказе мы будем постепенно двигаться от основ к более сложным вещам:
Установим среду разработки —
Android Studio
Скачаем и настроем проект
Научимся обращаться к элементам и напишем первый тест
Разберем, как писать
page object
и для чего они нужныПоработаем со списками
Обсудим стабильность автотестов на
Kaspresso
Как подготовиться?
Если у вас еще не установлена Android Studio
, то ее можно скачать с официального сайта
Далее, нам нужен код приложения, которое мы будем тестировать. Это может быть вашим рабочим проектом. Если его нет, то вы можете скачать тестовый проект-репозиторий Kaspresso, примеры из которого я буду сегодня разбирать.
Сделать это можно так: Code - Local - Https - копировать путь
:
После этого идем в Android Studio
, жмем Get from VSC
(или File > New > Project from Version Control, если у вас уже открыт какой то проект):
После клонирования проекта у нас должен открыться readme
файл.
Как запустить приложение?
Воспользуемся заготовленным для нас приложением от Kaspresso
— Tutorial
. Тестировать мы будем на эмуляторе, который для начала нужно создать. Для этого идем в device manager
и жмём Create device
:
У симуляторов много разных настроек (версия андроида, разрешение экрана, количество памяти и тд), но для первого автотеста нам все это не очень важно, поэтому можно создать абсолютно любой, не меняя никаких настроек, то есть просто прокликать next - next - finish
. Когда эмулятор будет создан, он будет отображаться в списке девайсов, как у меня на скриншоте выше.
Далее запускаем приложение. Для этого выбираем в списке девайсов наш созданный девайс — эмулятор, проверяем, что в конфигурации выбрано tutorial
, после чего жмем кнопку Run
:
После того, как проект успешно соберется, у нас должно появиться окошко с эмулятором и запущенным тестовым приложением, которое выглядит примерно так (в зависимости от созданного эмулятора):
Главный экран приложения состоит из кнопок, по нажатию на которые мы сможем проверять разные действия. Возьмем за сценарий для первого автотеста простой клик на кнопку Simple Test
и проверим, что произойдет.
Как это сделать?
Для того, чтобы уметь делать клики и проверки, воспользуемся возможностями Kaspresso
. Для этого нам нужно добавить в проект зависимости в файле build.gradle.kts(tutorial)
, после чего можно будет приступать к автоматизации тестирования:
androidTestImplementation("com.kaspersky.android-components:kaspresso:1.5.2")
androidTestUtil("androidx.test:orchestrator:1.4.2")
Если на момент прочтения этой статьи выйдут новые версии зависимостей, Android Studio
вам предложит их выставить.
После правок файл будет выглядеть вот так:
Как видно, мы добавили на 34 и 35 строки нужные зависимости. Чтобы эти зависимости «подтянулись» и вступили в силу, нажимаем кнопку Sync Now
.
Далее создадим папку в нашем проекте, где будут лежать автотесты — жмем правой кнопкой мыши на папку main - new - directory
, и в появившемся окне вводим и выбираем androidTest/kotlin
:
Несмотря на то, что у нас будет всего один автотест, добавим подпапку для автотестов — на реальном проекте это будет очень удобно. Для этого кликаем правой кнопкой по папке Kotlin - new - Package
, и вводим название — com.kaspersky.kaspresso.tutorial.test
. Далее по аналогии добавим файл для теста: папка kotlin - new - Kotlin Class/File
, назовем его SimpleTest
. При именовании классов автотестов хорошей практикой является добавление в конце слова Test
. Часто названия состоят из двух и более слов, чтобы детальнее донести суть проверок, например ProlongateVacancyIfMoneyExistsTest
.
Все автотесты должны наследоваться от класса TestCase
, давайте выполним это требование:
В kotlin
можно унаследовать класс от другого с помощью символа двоеточия в формате Класс наследник : Класс родитель
. Обратите внимание, что IDE
предложит нам несколько вариантов импортов, нам нужен вариант — com.kaspersky.kaspresso.testcases.api.testcase.TestCase
, выберем нужное нам из списка — делаем двойной клик на отмеченное предложение. После чего, студия автоматически добавит нам импорт нужного класса с помощью ключевого слова import
:
Добавим в нашем классе функцию, которая будет отвечать за запуск автотеста, и пометим ее аннотацией @Test (пакет junit.framework
). Наш тест готов:
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import org.junit.Test
class SimpleTest : TestCase() {
@Test
fun test() {
}
}
Мы уже можем его запустить и убедиться, что тест проходит, так как он пока не имеет никакой логики. Следующим шагом нужно наполнить его действиями и проверками, чтобы наш автотест начал что то проверять.
Как обращаться к элементам?
Основная парадигма как веб, так и мобильных автотестов — это обращение к элементам по их id
. Нам нужно решить, к какому элементу мы хотим обращаться, например, button
или title
, и найти их id
. У каждого элемента в приложении есть уникальный идентификатор, по которому можно обращаться и совершать действия над нужным конкретным элементом. Этот механизм гарантирует, что мы обратимся к интересующему нас элементу, а не к первому попавшемуся на экране.
Для простоты рассмотрим вариант, когда все айди элементов описываются прямо внутри класса автотеста, то есть в SimpleTest
. Находим идентификатор нужной нам кнопки:
Разберем по шагам, что происходит на видео:
Разберем по шагам, что происходит на видео:
Для запуска
layout inspector
'а — инструмента для просмотра иерархии элементов приложения — должно быть запущено приложение, на экране которого мы хотим искать нашиid
. Запускаем его через кнопкуrun
.После запуска открываем
layout inspector
— он находится во вкладкеTools - Layout inspector
.Для экономии места на экране можно свернуть вкладку
Project
, так как она нам пока не понадобится.Layout inspector
состоит из трех вкладок:Component tree
, в котором мы видим структуру открытого экрана; область с самим экраном;Attributes
, в котором можно найти интересующие насid
и другие свойства элементов. На записи видно, что при нажатии на разные элементы приложения в панелиComponent tree
можно увидеть ихid
и другие атрибуты в панелиAttributes
.
Добавляем кнопку в автотест:
Чтобы автотест мог обратиться к какому-либо элементу по его
id
, нужно сделатьimport
пакета/директории, в котором находится этотid
в коде приложения. Выбираем кнопку с названиемSimple Test
, на которую хотим тапнуть в тесте, вLayout Inspector
. Раскрываем строку с id во вкладкеAttributes
, и нажимаем на ссылкуactivity_main.xml
. Грубо говоря, это файл, отвечающий за верстку экрана с кнопками, где описаны все элементы. Далее нужно найти файлAndroidManifest.kts
из модуля, в котором находится наша кнопка. Сделать это можно и через поиск, но в больших проектах файлов с таким названием будет несколько, а нам нужен файл для модуляmain
, в котором содержится файлactivity_main.xml
. Для этого сворачиваемlayout inspector
, открываем вкладкуProject
, и нажимаем на кнопку «мишень», которая показывает, где в структуре нашего проекта находится открытый файл. Далее мы видим, что файлactivity_main.xml
находится в папкеmain
, и для этой папки определен нужный намAndroidManifest.kts
. Открываем его и копируем название пакета.Возвращаемся в класс с автотестом, добавляем
import
пакета и.R
на конце.R
— автоматически генерируемый класс, который содержит ссылки на такие ресурсы, как макеты, изображения, строки, цвета и другие ресурсы, используемые в приложении. Как будет видно дальше, с помощьюR
мы будем обращаться кid
элемента.Создаем переменную для кнопки с помощью ключевого слова
KButton
— это один из поддерживаемых виджетовAndroid
вKaspresso
, более подробно о них можно прочитать здесь. Внутри блокаKButton
с помощью конструкцииwithId(R.id.simple_activity_btn)
говорим, что у нашей кнопки айдиsimple_activity_btn
.После этого вызываем у
simpleButton
методclick()
для клика по элементу.Осталась последняя деталь, не попавшая на видео: нужно добавить правило, которое обеспечивает управление
activity MainActivity
в автотесте. Это нужно для возможности взаимодействия с интерфейсом этой активити в приложении, в нашем случае это наш главный экран.
Получившийся автотест будет выглядеть так:
import androidx.test.ext.junit.rules.activityScenarioRule
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import com.kaspersky.kaspresso.tutorial.MainActivity
import com.kaspersky.kaspresso.tutorial.R
import io.github.kakaocup.kakao.text.KButton
import org.junit.Test
import org.junit.Rule
class SimpleTest : TestCase() {
@get:Rule
val activityRule = activityScenarioRule()
@Test
fun test() {
val simpleButton = KButton {
withId(R.id.simple_activity_btn)
}
simpleButton.click()
}
}
Запустив по нажатию на кнопку run
напротив fun test()
, увидим работу теста в эмуляторе, и, что тест passed
:
Готово! У нас появился первый рабочий тест. Обычно в автотесте намного больше элементов и, следовательно, обращений к ним по id
. Это могло бы существенно перегрузить класс автотеста и сделать его практически нечитаемым и неподдерживаемым. К тому же, у нас может быть несколько автотестов, которые будут обращаться к одним и тем же элементам, и повторно описывать эти элементы для них было бы очень неудобно и трудозатратно. Все эти проблемы решает такой паттерн, как page object
.
Что за page object?
Этот подход подразумевает, что моделируемый класс будет полностью описывать один экран тестируемого приложения — все элементы экрана и методы для взаимодействия с этими элементами. Таким образом, нам не придется каждый раз заново объявлять одни и те же элементы в своих автотестах.
Давайте перепишем наш тест с использованием page object
и добавим дополнительные проверки.
Разберем по шагам, что происходит на видео:
Хранить все
page object
удобно в одной папке — создадим для этого папкуscreen
. Берем название пакета из файлаMainActivity.kt
, копируем путь пакета, и создаем вandroidTest/kotlin package
— вставляем скопированный путь и добавляем на конце .screen.Создаем класс в папке:
new - kotlin/java class
— выбираем типobject
, и вводим названиеMainScreen
. Хорошая практика нэйминга — добавлять в конце названия файлаScreen
, так все названия экранов будут выглядеть единообразно и их будет удобно искать, когда проект разрастется.Далее мы должны указать, что наш объект — это экран. Для этого наследуем наш
MainScreen
от классаScreen
.
Следующим шагом перенесем id
кнопки и обращение к ней из файла автотеста в page object
.
Разберем по шагам, что происходит на видео:
Переносим объявление кнопки из файла
SimpleTest
вpage object MainScreen
. Необходимые импортыR
иKButton
будут добавленыIDE
автоматически.Убираем ненужные импорты из
SimpleTest
.Прописываем нажатие на кнопку
simpleButton
. Для этого обращаемся к классуMainScreen
, у класса обращаемся к нужному нам полю класса —simpleButton
, и вызываем уsimpleButton
методclick
.
Такая запись выглядит более компактной и удобной для чтения. Запустив тест, можно убедиться, что он все так же работает.
Зачем добавлять в page object методы?
Один из принципов паттерна page object
подразумевает инкапсуляцию логики работы с элементами. Инкапсуляция — это один из принципов объектно ориентированного программирования, или ООП, который помогает организовать код так, чтобы скрыть детали его работы от внешнего мира. Другими словами, это создание «капсулы» вокруг данных и функций, чтобы предотвратить их случайное изменение или неправильное использование.
Это можно сравнить с тем, как работает пульт от телевизора. У вас есть несколько кнопок, но вы не видите, как они работают внутри пульта. Вам и не нужно знать, как каждая кнопка взаимодействует с телевизором. Вы просто нажимаете на кнопку, и телевизор делает то, что нужно.
Точно так же в программировании инкапсуляция позволяет скрыть сложные детали работы программы, предоставляя пользователю (или другим частям программы) только те функции, которые им нужны. Это делает код более безопасным и удобным в использовании, так как предотвращает ошибки и упрощает взаимодействие с программой.
Скрытие реализации достигается с помощью модификаторов доступа, нам будут интересны модификаторы public
и private
. Об остальных модификаторах можно почитать в официальной документации kotlin. Тут все интуитивно понятно — к членам класса с модификатором private
можно обратиться только внутри самого класса, в котором они объявлены. А к членам класса с модификатором public
— из любого класса приложения.
Теперь добавим в классе MainScreen
модификатор private
к полю simpleButton
:
private val simpleButton = KButton { withId(R.id.simple_activity_btn) }
Вернемся обратно в класс теста — IDE
сообщает, что мы не можем обратиться к приватному элементу внутри MainScreen
:
Вот теперь нам и пригодятся методы, которые будут нашим «пультом от телевизора», и будут иметь модификатор доступа public
.
Добавим метод, который будет делать клик по кнопке simpleButton
:
import com.kaspersky.kaspresso.tutorial.R
import io.github.kakaocup.kakao.screen.Screen
import io.github.kakaocup.kakao.text.KButton
import io.github.kakaocup.kakao.text.KTextView
object MainScreen : Screen() {
private val simpleButton = KButton { withId(R.id.simple_activity_btn) }
public fun clickSimpleButton() {
simpleButton.click()
}
}
Когда вы добавите этот код к IDE
, модификатор public
подсветится серым цветом, что означает что он не нужен, и метод по умолчанию public
. Поэтому в данном случае модификатор можно опустить.
Вернемся в класс SimpleTest
и вызовем метод clickSimpleButton
у класса page object MainScreen
:
import androidx.test.ext.junit.rules.activityScenarioRule
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import com.kaspersky.kaspresso.tutorial.MainActivity
import com.kaspersky.kaspresso.tutorial.screen.MainScreen
import org.junit.Test
import org.junit.Rule
class SimpleTest : TestCase() {
@get:Rule
val activityRule = activityScenarioRule()
@Test
fun test() {
MainScreen.clickSimpleButton()
}
}
Готово! Мы научились инкапсулировать логику работы с приватными элементами экрана (телевизором) с помощью публичных методов (пульта от телевизора).
Добавим еще методов?
Все операции с элементами в Kaspresso
делятся на actions
и assertions
, то есть действия (например click
, который мы использовали), и проверки. Обычно в работе требуется что то более сложное, чем просто нажатие на элемент — например, проверить, что после совершенного действия произошло то, что мы ожидали, или, что элемент с конкретным id
имеет определенный текст и отображается на экране. Эти проверки удобно объединять в один метод.
Добавим в наш page object
такой метод для проверки title
на главном экране приложения. Id
для него находим точно так же, как и для кнопки, попутно удостоверившись, что лежит он в том же пакете и, следовательно, будет найден по тому же самому R
, что и кнопка. Не забываем объявить screenTitle
как private
поле.
Часть page object
с методом проверки title
будет выглядеть так:
private val screenTitle = KTextView { withId(R.id.title) }
fun checkTitle(title: String) {
screenTitle {
isDisplayed()
hasText(title)
}
}
Вынесем клик по кнопке также в отдельный метод.
Итого класс page object
будет выглядеть следующим образом:
package com.kaspersky.kaspresso.tutorial.screen
import com.kaspersky.kaspresso.tutorial.R
import io.github.kakaocup.kakao.screen.Screen
import io.github.kakaocup.kakao.text.KButton
import io.github.kakaocup.kakao.text.KTextView
object MainScreen : Screen() {
private val simpleButton = KButton { withId(R.id.simple_activity_btn) }
private val screenTitle = KTextView { withId(R.id.title) }
fun clickSimpleButton() {
simpleButton.click()
}
fun checkTitle(title: String) {
screenTitle {
isDisplayed()
hasText(title)
}
}
}
import androidx.test.ext.junit.rules.activityScenarioRule
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import com.kaspersky.kaspresso.tutorial.MainActivity
import com.kaspersky.kaspresso.tutorial.screen.MainScreen
import org.junit.Test
import org.junit.Rule
class SimpleTest : TestCase() {
@get:Rule
val activityRule = activityScenarioRule()
@Test
fun test() {
MainScreen {
checkTitle("Tutorial")
clickSimpleButton()
}
}
}
Таким образом, теперь наглядно видно, что page object
— это набор приватных полей и публичных методов для работы с ними в рамках одного экрана.
Обратите внимание, что в этом примере title
вынесен во входной параметр метода — title: String
, и может задаваться динамически из автотеста. Однако его можно «захардкодить», задать статически внутри метода page object
, как hasText("Tutorial”)
. Работать будет и так, и так — выбор зависит от того, будет ли меняться текст на TextView
. Класс автотеста тоже преобразился — с использованием page object
и методов код стал еще более компактным и удобочитаемым.
Типичная структура автотеста будет выглядеть так:
@Test
fun test() {
firstScreen {
//проверки и действия, например поиск title и открытие следующего экрана
}
secondScreen {
//проверки и действия, например, что то вводим, сохраняем и открываем предыдущий экран
}
firstScreen {
//проверяем, что состояние экрана изменилось
}
}
Как работать со списками?
В андроид разработке часто используется такой компонент пользовательского интерфейса, как RecyclerView
, который по своей сути является прокручиваемым списком элементов. Этот компонент накладывает некоторые особенности при тестировании, давайте разберем их.
Откроем приложение Tutorial
и кликнем по кнопке List Activity
. Откроется экран со списком дел пользователя. У каждого элемента списка есть порядковый номер, текст и цвет. Также имеется возможность удалять элементы списка при помощи свайпа.
Напишем page object
для этого экрана. Открыв layout inspector
, можно увидеть, что все элементы списка лежат внутри RecyclerView
, у которого id: rv_notes
. Внутри него лежит три объекта, у которых одинаковые идентификаторы: note_container
(id
самой вьюшки), содержащий tv_note_id
(id
порядкового номера) и tv_note_text
(idтекста заметки):
Соответственно, протестировать экран обычным способом у нас не получится, так как элементы повторяются и имеют один и тот же id
. Вместо этого мы используем другой подход. В page object
списка будут содержаться объявленная переменная recyclerView
и класс айтема (ItemScreen) списка, внутри которого будут перечислены его элементы. То есть один Item
— это одна заметка в нашем случае, список (RecyclerView) — набор таких айтемов (заметок).
Создаем page object NoteListScreen
, и добавим код для описания RecyclerView
.
object NoteListScreen : KScreen() {
override val layoutId: Int? = null
override val viewClass: Class<*>? = null
val rvNotes = KRecyclerView(
builder = { withId(R.id.rv_notes) },
itemTypeBuilder = { itemType(::NoteItem) }
)
class NoteItem(matcher: Matcher) : KRecyclerItem(matcher) {
val noteContainer = KView(matcher) { withId(R.id.note_container) }
val tvNoteId = KTextView(matcher) { withId(R.id.tv_note_id) }
val tvNoteText = KTextView(matcher) { withId(R.id.tv_note_text) }
}
}
Разберем, что означает этот код:
val rvNotes = KRecyclerView(...)
: Здесь создается экземпляр классаKRecyclerView
. Он потребуется нам для взаимодействия с элементами вRecyclerView
, например, для прокрутки и выбора элементов.builder = { withId(R.id.rv_notes) }
: Эта часть определяет, как найтиRecyclerView
в пользовательском интерфейсе. Этот принцип поиска поid
нам уже знаком.itemTypeBuilder = { itemType(::NoteItemScreen) }
: Это запись определяет, из каких элементов состоит нашRecyclerView
.NoteItemScreen
— это пользовательский класс, который используется для описания элемента списка.class NoteItemScreen(matcher: Matcher
: Этот класс представляет элемент списка в) : KRecyclerItem (matcher) RecyclerView
. Он наследует отKRecyclerItem
и принимает как входной параметрmatcher
, который используется для определения того, как именно искать этот элемент в пользовательском интерфейсе.val noteContainer = KView(matcher) { withId(R.id.note_container) }
: Здесь определен элементnoteContainer
, который представляет собой контейнер в элементе списка. Грубо говоря, этоview
элемента списка — заметки.val tvNoteId = KTextView(matcher) { withId(R.id.tv_note_id) }
: Это текстовое полеtvNoteId
, которое содержит порядковый номер заметки.val tvNoteText = KTextView(matcher) { withId(R.id.tv_note_text) }
: Аналогично, это текстовое полеtvNoteText
, содержащее текст заметки.
Обратите внимание на два важных момента:
Первое: в конструктор View
-элементов необходимо передать matcher
, в котором будем произведен поиск необходимого объекта. Если этого не сделать, тест завершится неудачно
Второе: если мы проверяем какое-то специфичное поведение элемента UI
, то указываем конкретного наследника KView (KTextView, KEditText, KButton...)
. Например, если мы хотим проверить наличие текста, то создаем KTextView
, у которого есть возможность получить текст. Весь список доступных виджетов в Kaspresso
находится здесь.
Если мы проверяем какие-то общие вещи, которые доступны во всех элементах интерфейса (цвет фона, размеры, видимость и т.д.), то можно использовать родительский KView
. В данном случае мы будем проверять тексты у tvNoteId
и tvNoteText
, поэтому указали тип KTextView
. А контейнер, в котором лежат эти TextView
, является экземпляром CardView
, у него мы будем проверять только цвет фона, каких-то специфичных вещей проверять у него нет необходимости, поэтому в качестве типа мы указали родительский — KView
.
Добавляем по аналогии с другими кнопками кнопку перехода на экран со списком в MainScreen
. А после добавляем проверку видимости всех элементов и того, что все они содержат какой-то текст:
class SimpleTest : TestCase() {
@get:Rule
val activityRule = activityScenarioRule()
@Test
fun test() = run {
MainScreen.clickListButton()
NoteListScreen {
rvNotes {
children {
noteContainer.isVisible()
tvNoteId.isVisible()
tvNoteText.isVisible()
tvNoteId.hasAnyText()
tvNoteText.hasAnyText()
}
}
}
}
}
Также мы можем проверить каждый элемент в отдельности, например, что каждая заметка содержит правильные тексты и цвета фона:
class SimpleTest : TestCase() {
@get:Rule
val activityRule = activityScenarioRule()
@Test
fun test() = run {
MainScreen.clickListButton()
NoteListScreen {
rvNotes {
childAt(0) {
noteContainer.hasBackgroundColor(android.R.color.holo_green_light)
tvNoteId.hasText("0")
tvNoteText.hasText("Note number 0")
}
childAt(1) {
noteContainer.hasBackgroundColor(android.R.color.holo_orange_light)
tvNoteId.hasText("1")
tvNoteText.hasText("Note number 1")
}
childAt(2) {
noteContainer.hasBackgroundColor(android.R.color.holo_red_light)
tvNoteId.hasText("2")
tvNoteText.hasText("Note number 2")
}
}
}
}
}
Как я описывал ранее, с помощью класса R
мы можем не только обращаться к id
элемента, но и к другим ресурсам, таким как цвета.
Обратите внимание, что в первом примере мы использовали конструкцию children<'Нужный item'>
, которая позволяет обращаться ко всем наследникам в списке, а во втором — конструкцию childAt<'Нужный item'>('Позиция')
, которая позволяет обращаться к нужному айтему по позиции, нумерация идет с нуля.
Это далеко не все функции для работы со списками, в Kaspresso их больше, например есть childWith<'Нужный item'>
— обращение в нем происходит к определенному айтему, после чего с ним можно выполнять действия, например:
someRecycler {
childWith {
withDescendant {
withText("some text")
}
}.perform {
checkBox.click()
}
}
Прочитать этот код можно так: «Найди мне айтем SomeItem
из ресайклера someRecycler
, который имеет текст some text
(часто выносится в параметр метода), и кликни на него».
Еще из полезных методов есть firstChild
(обращение к первому элементу), lastChild
(обращение к последнему элементу) и другие. Их использование зависит от конкретных целей и проверок, которые вам нужно будет реализовать.
Если вы сомневаетесь, какая функция вам лучше подойдет, то нажмите прямо в студии на интересующую функцию с помощью command + click
для macOS
или Alt + click
для windows
и Linux
, и прочитайте открывшуюся документацию по функции.
Итак, наш первый автотест готов. Теперь можно смело его запускать и радоваться, что в ручном регрессе стало меньше проверок. Но бывают ситуации, когда тест начинает вести себя нестабильно, что сильно омрачает нашу радость…
А что со стабильностью?
Флак или flakiness
— это когда ваш тест успешно выполняется десять раз, а на одиннадцатый падает по непонятной причине. Одна из основных причин такого поведения — это когда автотест не дожидается искомого элемента на экране. Так вот Kaspresso
имеет встроенную защиту от флаков в тестах.
Разберем этот механизм на примере и напишем автотест на проверку текста, который появляется на экране с задержкой. Чтобы попасть на нужный экран, нам нужно нажать на кнопку Flaky Activity
. Для этого по аналогии с прошлыми примерами добавим в page object
основного экрана нужную нам кнопку:
private val flakyButton = KButton { withId(R.id.flaky_activity_btn) }
После этого добавим в тест нажатие данной кнопки:
@Test
fun test() = run {
MainScreen.clickFlakyButton()
}
Можно запустить автотест и убедиться, что он открывает нужный нам экран:
Обратите внимание, что текст на TextView
появляется с задержкой.
Теперь нужно написать page object
на этот экран. Делаем по аналогии с предыдущими примерами. Экран можно назвать FlakуScreen
, должно получиться что-то наподобие:
object FlakyScreen : Screen() {
private val flakyText = KTextView { withId(R.id.text_1) }
fun checkFlakyText() {
flakyText {
hasText("TEXT1")
}
}
}
Теперь добавим проверку, что текст соответствует ожидаемому нами, то есть TEXT1
для первого textView
:
fun checkFlakyText() {
flakyText {
hasText("TEXT1")
}
}
Функция checkFlakyText
содержит вызов функции hasText
, которая проверяет, содержит ли выбранная TextView
строго заданный нами текст. Добавим все это в автотест:
@Test
fun test() = run {
MainScreen.clickFlakyButton()
FlakyScreen.checkFlakyText()
}
Запустим тест и убедимся, что фреймворк «дожидается» появления искомого текста и успешно проходит. Таким образом, Kaspresso
под капотом содержит десятисекундное ожидание появления нужной нам view
(элемента), что обеспечивает хорошую стабильность.
Иногда могут возникнуть ситуации, когда стандартных десяти секунд может не хватать, например, когда в мобильном приложении начинается загрузка какого-либо содержимого с сервера. Чтобы автотест в этом месте не упал по таймауту, можно воспользоваться функцией flakySafely
, с помощью которой можно выставить свое время ожидания. Для этого в автотесте должен присутствовать блок run
. Это один из трех блоков, помогающий управлять состоянием приложения, в нем описываются основные действия и логика. В блоке before
задаются настройки, состояние до запуска теста, а в блоке after
можно вернуть настройки к первоначальному состоянию после прохождения теста, например, удалить созданные тестовые данные, так как это не входит в основную логику сценария.
Итак, добавляем функцию flakySafely
и блок run
в наш код с параметром 15000
миллисекунд:
@Test
fun test() = run {
MainScreen.clickFlakyButton()
flakySafely(15000) {
FlakyScreen.checkFlakyText()
}
}
Запускаем, и проверяем, что тест все так же проходит.
Механизм встроенной защиты от флакований flakySafely
неявно вызывается при каждой проверке со стандартным значением 10 секунд. Таким образом, явный вызов flakySafely
нужно использовать только в тех случаях, когда стандартных десяти секунд не хватает для загрузки какого — либо содержимого.
Иногда в интернете встречаются советы, что вместо flakySafely
проще использовать метод sleep
класса Thread
из Java
в формате Thread.sleep(15000)
. Это плохой совет и им не нужно пользоваться. Да, вы добьетесь почти того же самого результата, что и с flakySafely
, но с той разницей, что flakySafely
«обрубит» ненужные секунды ожидания и приступит к следующему шагу сразу, как найдет нужный элемент на экране, в то время как sleep
выждет отведенные секунды в любом случае. Чем больше в вашем проекте будет автотестов с использованием sleep
, тем дольше они будут проходить.
Чему мы научились?
Это далеко не все возможности фреймворка, но, есть большая надежда, что этих знаний вам хватит для запуска первых тестов и дальнейшего их (как знаний, так и тестов) масштабирования.
Итак, основные «заповеди» при написании автотестов на фреймворке Kaspresso
:
Обращаемся к элементам
UI
поid
, в поиске которых нам помогаетlayout inspector
.Описываем экраны с помощью паттерна
page object
, парадигма которого «один класс — один экран», храним их в отдельной папке.При добавления элемента в
page object
нам нужно импортировать находящийся в нужном пакете иерархии классR
, который позволит обращаться к ресурсам приложения.При написании
page object
необходимо инкапсулировать логику работы с экраном с помощью приватных и публичных модификаторов доступа для элементов и методов соответственно.Со списками можно работать с помощью большого набора встроенных функций, которые можно выбирать в зависимости от целей.
Kaspresso
имеет встроенную защиту от флакований, обеспечивающую стабильность тестов.
Спасибо за прочтение, пишите в комментариях, с какими трудностями вы столкнулись при написании своих первых автотестов.
Увидимся в следующих выпусках!