Ваш первый тест на Kaspresso
Переключаемся на нужную ветку в GIT
В Android Studio вы можете переключаться между ветками и, таким образом, видеть разные версии проекта. Изначально, после загрузки Kaspresso вы будете находиться в главной ветке - master
.
В этой ветке находится исходный код приложения, которое мы будем покрывать тестами. В текущем и последующих уроках будет приведена пошаговая инструкция в формате codelabs по написанию автотестов. Итоговый результат со всеми написанными тестами доступен в ветке TECH-tutorial-results
, вы в любой момент сможете переключиться на нее и посмотреть решение.
Для этого кликните на название ветки, в которой находитесь, и в поиске введите название интересующей вас ветки.
Ручное тестирование
Прежде чем приступать к написанию теста, давайте поближе познакомимся с функционалом, который мы будем покрывать автотестами. Для этого переключаемся на master-ветку.
Открываем выбор конфигурации (1) и выбираем tutorial (2):
Проверяем, что выбран нужный девайс (1) и запускаем приложение (2):
После успешного запуска приложения мы видим основной экран приложения Tutorial.
Нажимаем на кнопку с текстом "Simple test" и видим следующий экран:
Экран состоит из:
1. Заголовка TextView
2. Поля ввода EditText
3. Кнопки Button
Info
Полный список виджетов в Android с подробной информацией можно найти здесь
При нажатии на кнопку текст в заголовке меняется на введенный в поле ввода.
Автоматическое тестирование
Мы вручную проверили, что результат работы приложения соответствует ожиданиям:
- На главном экране есть кнопка перехода на экран SimpleTest (остальные элементы этого экрана нас сейчас не интересуют)
- Эта кнопка видима
- На нее можно кликнуть
- При клике на нее мы переходим на экран SimpleTest
- На экране SimpleTest есть элементы - заголовок, поле ввода и кнопка
- Все эти элементы видимы
- Заголовок содержит текст по умолчанию
- Если ввести какой-то текст в поле ввода и кликнуть на кнопку, то текст в заголовке меняется на введенный
Теперь нам нужно все те же проверки написать в коде, чтобы они осуществлялись в автоматическом режиме.
Чтобы покрыть приложение тестами Kaspresso, необходимо начать с подключения библиотеки Kaspresso в зависимостях проекта.
Подключаем Kaspresso к проекту
Переключаем отображение файлов проекта как Project (1) и добавляем зависимость в существующую секцию dependencies
в файле build.gradle
модуля Tutorial
:
dependencies {
androidTestImplementation("com.kaspersky.android-components:kaspresso:1.5.1")
androidTestUtil("androidx.test:orchestrator:1.4.2")
}
Написание теста начнем с создания Page object для текущего экрана.
Можем писать код нашего теста. Чтобы это сделать, необходимо для каждого экрана, который участвует в тесте, создать модель (класс), внутри которого объявить все элементы интерфейса (кнопки, текстовые поля и т.д.), из которых состоит экран, с которыми будет взаимодействовать тест. Такой подход называется Page Object
и подробнее о нем вы можете почитать в документации.
В первых четырех пунктах теста мы взаимодействуем с главным экраном, поэтому первым делом необходимо создать Page Object главного экрана.
Работать мы будем в папке androidTest
в модуле tutorial
. Если у вас этой папки нет, то ее необходимо создать, для этого кликаем правой кнопкой мыши на папку src
и выбираем пункт New
-> Directory
.
Выбираем пункт androidTest/kotlin
:
Внутри папки kotlin давайте создадим отдельный пакет (package), в котором будем хранить все Page Object-ы:
Создание отдельного пакета на функциональность не влияет, мы это делаем просто для удобства, чтобы все модели экранов лежали в одном месте. Вы можете дать пакету любое имя (за некоторым исключением), но обычно в тестах используют такие же названия, как в самом приложении. Мы можем перейти в файл MainActivity, где сверху будет указано имя пакета.
Копируем это имя и вставляем в название пакета. Конкретно в этом пакете мы будем хранить только модели экранов (Page Object-ы), поэтому в конце давайте добавим .screen
.
Когда мы будем добавлять другие классы в папку с тестами, то будем класть их уже в другие пакеты, но при этом первая часть их названия будет такой же com.kaspersky.kaspresso.tutorial
.
Теперь в созданном пакете мы добавляем модель экрана (класс):
Выбираем тип Object и именуем MainScreen.
MainScreen представляет собой модель главного экрана. Для того чтобы эту модель можно было использовать в автотестах, необходимо унаследоваться от класса KScreen и в угловых скобках указать название этого класса.
Info
Указание типа в угловых скобках в Java и Kotlin называется Generics. Подробнее об этом вы можете почитать в документации по Java и Kotlin
package com.kaspersky.kaspresso.tutorial.screen
import com.kaspersky.kaspresso.screens.KScreen
object MainScreen : KScreen<MainScreen>() {
}
ctrl + i
и выбрать элементы, которые мы хотим переопределить.
Удерживая ctrl
, выбираем все пункты и нажимаем OK
.
package com.kaspersky.kaspresso.tutorial.screen
import com.kaspersky.kaspresso.screens.KScreen
object MainScreen : KScreen<MainScreen>() {
override val layoutId: Int?
get() = TODO("Not yet implemented")
override val viewClass: Class<*>?
get() = TODO("Not yet implemented")
}
В файле появились новые строчки кода. Вместо TODO
нужно написать корректную реализацию - id макета (layoutId
), который установлен на экране, и название класса (viewClass
). Это необходимо для связывания теста с конкретным файлом верстки и классом activity. Такое связывание сделает дальнейшую поддержку и доработку теста более удобной, но пока перед нами стоит задача написать первый тест, поэтому оставим значение null
.
package com.kaspersky.kaspresso.tutorial.screen
import com.kaspersky.kaspresso.screens.KScreen
object MainScreen : KScreen<MainScreen>() {
override val layoutId: Int? = null
override val viewClass: Class<*>? = null
}
Теперь внутри класса KScreen мы будем объявлять все элементы пользовательского интерфейса, с которыми будет взаимодействовать тест. В нашем случае на главном экране нас интересует только кнопка SimpleTest
.
Чтобы тест мог с ней взаимодействовать, нужно знать id, по которому эту кнопку можно найти на экране. Эти идентификаторы присваивает разработчик при написании приложения.
Чтобы узнать, какой id был присвоен какому-то элементу интерфейса, можно воспользоваться инструментом, встроенным в Android Studio - LayoutInspector
.
- Запускаем приложение
- В правом нижнем углу Android Studio выбираем пункт Layout Inspector
- Ждем пока загрузится экран
- Если экран не загрузился, то проверьте, что у вас выбран нужный процесс
Ищем пункт id - это тот идентификатор, который нас интересует.
Также важно понимать, с каким элементом UI мы работаем. Для этого можно перейти в макет, где элемент был объявлен, и посмотреть всю информацию о нем.
В данном случае это элемент Button c вот таким id: simple_activity_btn
Можем добавлять эту кнопку в MainScreen
, обычно название переменной дают такое же, как id, но без нижних подчеркиваний, каждое следующее слово с заглавной буквы (это называется camelCase)
package com.kaspersky.kaspresso.tutorial.screen
import com.kaspersky.kaspresso.screens.KScreen
object MainScreen : KScreen<MainScreen>() {
override val layoutId: Int? = null
override val viewClass: Class<*>? = null
val simpleActivityButton =
}
package com.kaspersky.kaspresso.tutorial.screen
import com.kaspersky.kaspresso.screens.KScreen
import com.kaspersky.kaspresso.tutorial.R
import io.github.kakaocup.kakao.text.KButton
object MainScreen : KScreen<MainScreen>() {
override val layoutId: Int? = null
override val viewClass: Class<*>? = null
val simpleActivityButton = KButton { withId(R.id.simple_activity_btn) }
}
Во-первых, давайте перейдем в определение KButton и посмотрим, что это. Для этого, удерживая ctrl
, кликаем на название класса KButton левой кнопкой мыши.
Видим, что это класс, который наследуется от KBaseView и реализует интерфейс TextViewAssertions. Можем перейти в определение KBaseView и посмотреть всех наследников этого класса, их тут достаточно много.
Зачем они все нужны?
Дело в том, что каждый элемент пользовательского интерфейса можно протестировать по-разному. К примеру, для TextView мы можем проверить, какой текст сейчас в него установлен, можем установить новый текст, а ProgressBar не содержит никакой текст, и осуществлять проверку на то, какой текст в него установлен, нет смысла.
Поэтому в зависимости от того, какой элемент интерфейса мы тестируем, нужно выбирать правильную реализацию KBaseView. Сейчас мы тестируем кнопку, поэтому выбрали KButton. На следующем экране мы будем тестировать заголовок (TextView) и поле ввода (EditText) и выберем соответствующие реализации KBaseView.
Идем дальше, эту кнопку тест должен найти на экране по какому-то критерию. В данном случае мы осуществим поиск элемента по id, поэтому используем матчер withId
, куда в качестве параметра передаем идентификатор кнопки, который мы нашли благодаря Layout Inpector
.
Для того чтобы указать этот id, мы использовали синтаксис R.id..., где R
- это класс со всеми ресурсами приложения. Благодаря ему можно находить id элементов интерфейса, строк, которые есть в проекте, картинок и т.д. При вводе названия этого класса Android Studio должна импортировать его автоматически, но иногда этого не происходит, тогда нужно ввести этот импорт вручную.
import com.kaspersky.kaspresso.tutorial.R
Все, теперь у нас есть модель главного экрана и эта модель содержит кнопку, которую можно тестировать. Можем приступать к написанию самого теста.
Добавляем SimpleActivityTest
В папке androidTest
-> kotlin
, в созданном нами пакете добавляем класс SimpleActivityTest
.
Новый класс был размещен в пакете screen
, но мы хотели бы, чтобы в нем лежали только модели экранов, поэтому созданный тест мы переместим в корень пакета com.kaspersky.kaspresso.tutorial
. Для того, чтобы это сделать, кликаем на название класса правой кнопкой мыши и выбираем Refactor
-> Move
И убираем из названия пакета последнюю часть .screen
.
Класс тестов должен быть унаследован от класса TestCase
. Обратите внимание на импорты, класс TestCase должен быть импортирован из пакета import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
.
package com.kaspersky.kaspresso.tutorial
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
class SimpleActivityTest: TestCase() {
}
И добавляем метод test()
, в котором будем проверять работу приложения. У него может быть любое имя, необязательно "test", но важно, чтобы он был помечен аннотацией @Test
(import org.junit.Test
).
package com.kaspersky.kaspresso.tutorial
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import org.junit.Test
class SimpleActivityTest : TestCase() {
@Test
fun test() {
}
}
Тест SimpleActivityTest
можно запустить. Информацию по запуску тестов в Android Studio можно найти в предыдущем уроке.
Сейчас этот тест ничего не делает, поэтому и завершается успешно. Давайте добавим ему логики и протестируем MainScreen.
package com.kaspersky.kaspresso.tutorial
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import org.junit.Test
import com.kaspersky.kaspresso.tutorial.screen.MainScreen
class SimpleActivityTest : TestCase() {
@Test
fun test() {
MainScreen {
simpleActivityButton {
isVisible()
isClickable()
}
}
}
}
Внутри метода test мы получаем объект MainScreen, открываем фигурные скобки и обращаемся к кнопке, которую будем тестировать, дальше открываем еще раз фигурные скобки и тут пишем все проверки. Сейчас, благодаря методам isVisible()
и isClickable()
мы проверяем, что кнопка видима и по ней можно кликнуть. Запускаем и наш тест падает.
Дело в том, что Page Object MainScreen
относится к MainActivity
(именно эту активити видит пользователь, когда запускает приложение) и, для того чтобы элементы отобразились на экране, эту активити нужно запустить перед выполнением теста. Для того, чтобы перед тестом была запущена какая-то активити, нужно добавить следующие строки:
@get:Rule
val activityRule = activityScenarioRule<MainActivity>()
Этот тест осуществит запуск указанной activity MainActivity
перед запуском теста и закроет после прогона теста.
Подробнее про activityScenarioRule
можно почитать здесь.
Тогда весь код теста будет выглядеть следующим образом:
package com.kaspersky.kaspresso.tutorial
import androidx.test.ext.junit.rules.activityScenarioRule
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import com.kaspersky.kaspresso.tutorial.MainActivity
import org.junit.Rule
import org.junit.Test
import com.kaspersky.kaspresso.tutorial.screen.MainScreen
class SimpleActivityTest : TestCase() {
@get:Rule
val activityRule = activityScenarioRule<MainActivity>()
@Test
fun test() {
MainScreen {
simpleActivityButton {
isVisible()
isClickable()
}
}
}
}
Запускаем. Все отлично, у нас тест проходит успешно, и вы можете увидеть на девайсе, что во время теста открывается нужная нам активити и закрывается после прогона.
Хорошей практикой во время написания тестов является проверка, что тест не только успешно выполняется, но и падает, если условие не выполняется. Так вы исключите ситуацию, когда тесты "зеленые", но на самом деле из-за какой-то ошибки в коде проверки вообще не выполнялись. Давайте это сделаем, проверим, что кнопка содержит некорректный текст.
class SimpleActivityTest : TestCase() {
@get:Rule
val activityRule = activityScenarioRule<MainActivity>()
@Test
fun test() {
MainScreen {
simpleActivityButton {
isVisible()
isClickable()
containsText("Incorrect text")
}
}
}
}
Тест падает, меняем текст на корректный.
class SimpleActivityTest : TestCase() {
@get:Rule
val activityRule = activityScenarioRule<MainActivity>()
@Test
fun test() {
MainScreen {
simpleActivityButton {
isVisible()
isClickable()
containsText("Simple test")
}
}
}
}
Тест проходит успешно.
Теперь нам нужно протестировать SimpleActivity
. Делаем по аналогии с MainScreen
- создаем Page Object.
object SimpleActivityScreen : KScreen<SimpleActivityScreen>() {
override val layoutId: Int? = null
override val viewClass: Class<*>? = null
}
Ищем id элементов через Layout Inspector
:
Не забываем указывать корректные View элементы, для заголовка - KTextView, для поля ввода - KEditText, для кнопки - KButton
object SimpleActivityScreen : KScreen<SimpleActivityScreen>() {
override val layoutId: Int? = null
override val viewClass: Class<*>? = null
val simpleTitle = KTextView { withId(R.id.simple_title) }
val inputText = KEditText { withId(R.id.input_text) }
val changeTitleButton = KButton { withId(R.id.change_title_btn) }
}
И теперь можем тестировать этот экран. Для того, чтобы на него перейти, на главном экране нужно кликнуть на кнопку, вызываем click()
.
Добавляем проверки для этого экрана:
package com.kaspersky.kaspresso.tutorial
import androidx.test.ext.junit.rules.activityScenarioRule
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import com.kaspersky.kaspresso.tutorial.MainActivity
import org.junit.Rule
import org.junit.Test
import com.kaspersky.kaspresso.tutorial.screen.MainScreen
import com.kaspersky.kaspresso.tutorial.screen.SimpleActivityScreen
class SimpleActivityTest : TestCase() {
@get:Rule
val activityRule = activityScenarioRule<MainActivity>()
@Test
fun test() {
MainScreen {
simpleActivityButton {
isVisible()
isClickable()
click()
}
}
SimpleActivityScreen {
simpleTitle.isVisible()
changeTitleButton.isClickable()
simpleTitle.hasText("Default title")
inputText.replaceText("new title")
changeTitleButton.click()
simpleTitle.hasText("new title")
}
}
}
Наш первый тест практически готов. Единственное изменение, которое стоит сделать - тут мы используем захардкоженный текст "Default title". При этом тест успешно проходит, но если вдруг приложение будет локализовано на разные языки, то при запуске теста с английской локалью тест может проходить успешно, а если запустим на устройстве с российской локалью, то тест упадет.
Поэтому вместо того, чтобы хардкодить строку, мы возьмем ее из ресурсов приложения. В макете активити мы можем посмотреть, какая строка использовалась в этом TextView.
Переходим в строковые ресурсы (файл values/strings.xml
) и копируем id строки.
Теперь в методе hasText вместо использования строки "Default title" используем ее id R.string.simple_activity_default_title
.
Не забываем импортировать класс ресурсов R import com.kaspersky.kaspresso.tutorial.R
.
Финальный код теста выглядит вот так:
package com.kaspersky.kaspresso.tutorial
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 org.junit.Rule
import org.junit.Test
import com.kaspersky.kaspresso.tutorial.screen.MainScreen
import com.kaspersky.kaspresso.tutorial.screen.SimpleActivityScreen
class SimpleActivityTest : TestCase() {
@get:Rule
val activityRule = activityScenarioRule<MainActivity>()
@Test
fun test() {
MainScreen {
simpleActivityButton {
isVisible()
isClickable()
click()
}
}
SimpleActivityScreen {
simpleTitle.isVisible()
changeTitleButton.isClickable()
simpleTitle.hasText(R.string.simple_activity_default_title)
inputText.replaceText("new title")
changeTitleButton.click()
simpleTitle.hasText("new title")
}
}
}
Итог
В этом уроке мы написали наш первый тест на Kaspresso. На практике познакомились с подходом PageObject. Научились получать идентификаторы элементов интерфейса при помощи Layout inspector
.