Если вы писали тесты на Espresso — тестовом фреймворке от Google с открытым исходным кодом, — то вы знаете, что они не всегда стабильны и легко читаемы. Меня зовут Ксения Никитина, я являюсь Android-разработчиком в мобильной команде «Лаборатории Касперского». В этой статье я предложу вам способ, как сделать так, чтобы ваши автотесты отвечали всем ключевым качествам: были хорошо читаемы, стабильны, логируемы, давали возможность делать скриншоты, работали с AndroidOS и, наконец, имели продуманную и понятную архитектуру.
![](https://habrastorage.org/webt/xy/0t/uz/xy0tuzp8haa81-g7_ucocicloou.png)
Итак, когда появляется необходимость в написании автотестов на Android, мы чаще всего обращаемся к фреймворку Espresso от Google (если вдруг вы не знакомы с Espresso, подробности можно найти в официальной документации). Espresso позволяет работать с элементами приложения нативно и методом белого ящика. Нужные элементы можно сначала находить на экране с помощью matcher'ов, а затем выполнять с ними различные действия или проверки.
Рассмотрим работу автотестов на конкретном примере. Запустить приложение и воспроизвести все описанные далее шаги можно самостоятельно, скачав исходники на нашем гитхабе и запустив проект Tutorial.
При открытии приложения мы видим экран, содержащий кнопку Internet Availability, при нажатии на которую мы переходим на экран проверки состояния Wifi.
![](https://habrastorage.org/webt/_h/xm/e_/_hxme_jaipswh3soy1whwpksunc.png)
Кнопка Check Wifi status кликабельна. После клика отображается текущий статус состояния Wifi.
![](https://habrastorage.org/webt/xt/ix/f3/xtixf3pwumgyv6vrk1bdvnbolcs.png)
Нам также важно, чтобы при повороте экрана текст, указывающий статус Wifi, не менялся.
![](https://habrastorage.org/webt/me/24/vq/me24vqyh6puuunekenwcpzomewa.png)
Итого, для проверки корректности работы приложения нам необходимо пройти следующие шаги:
Давайте попробуем автоматизировать этот текстовый тест-кейс и на его примере обсудим имеющиеся недостатки Espresso.
Наш тестовый класс с использованием фреймворка Espresso:
В этом примере мы столкнулись с тем, что невозможно реализовать тест только средствами Espresso, поэтому нам пришлось воспользоваться библиотекой UiAutomator. Тест запускается и позволяет проверить корректность работы приложения, но, к сожалению, написанный код имеет несколько недостатков.
Что нам хотелось бы улучшить в написанном коде: разделить логику описания элементов и последовательность действий и проверок, использовать декларативный стиль написания тестов, избавиться от большого количества повторяющихся вызовов onView и иерархической вложенности. К сожалению, покрыть все потребности в автотестировании Android только с помощью Espresso невозможно из-за отсутствия определенных фич.
Попробуем переписать код нашего теста, прибегнув к помощи open-source-фреймворка Kaspresso, который использует декларативный подход к написанию тестов. Давайте рассмотрим на конкретном участке кода.
Было так:
А станет вот так:
Kaspresso использует под капотом Kakao — Kotlin-DSL-обертку над Espresso. От Kakao open-source-фреймворк Kaspresso унаследовал две основные концепции. Первая — KView — особое представление элементов интерфейса, с которыми будет происходить взаимодействие в тесте. Использование KView избавляет нас от постоянного вызова метода onView, теперь достаточно всего один раз положить matcher’ы в конструктор KView.
Вторая концепция — класс Screen (реализация паттерна Page Object), где описываются все элементы, с которыми будет происходить взаимодействие во время теста. Концепция пришла из веб-разработки, она заключается в создании описания экрана, видимого пользователю. Этот объект не содержит никакой логики. Это позволяет в отдельном файле описать скрины (Screens) и их элементы и взаимодействовать с ними из кода класса теста в декларативном стиле. Таким образом, Page Object’ы являются абсолютно независимыми, за счет чего достигается максимальная переиспользуемость.
Давайте перепишем наш тестовый класс, используя фреймворк Kaspresso. Если у вас возникнут сложности с одним из последующих шагов, вы можете найти готовый вариант автотеста в ветке tutorial_results.
Первым делом надо создать Page Object MainScreen:
Здесь мы встречаемся с необходимостью определить id макета (layoutId), который установлен на экране, и название класса (viewClass). Это требуется для связывания теста с конкретным файлом верстки и классом activity или fragment. Такое связывание сделает дальнейшую поддержку и доработку теста более удобной, но пока перед нами стоит задача рассмотреть демонстрационный тестовый класс, поэтому оставим значение null.
Далее создадим WifiScreen:
Теперь займемся тест-кейсами. Класс тестов должен быть унаследован от класса TestCase. Создаем такой класс WifiSampleTest и добавляем метод, помеченный аннотацией Test (import org.junit.Test), в котором будем проверять работу приложения.
Ранее мы упоминали, что Espresso не умеет работать с Android OS, вследствие чего пользователю приходится использовать библиотеку UiAutomator. Обратите внимание, что в коде теста используется экземпляр класса Device, являющегося частью фреймворка Kaspresso. У этого объекта есть множество полезных методов, подходящих для взаимодействия с Android OS. Подробнее про них вы можете почитать здесь.
Что мы получили?
Код тестового класса уже стал выглядеть лучше, но в нем все еще присутствуют проблемные места. Обычно любые тесты (в том числе ручные) выполняются по тест-кейсам. То есть у тестировщика есть последовательность шагов, которые он выполняет для проверки работоспособности экрана. Теперь представим, что наш тестовый класс содержит в несколько раз больше шагов и, соответственно, строк кода. Разобраться и добавить необходимые строчки в таком случае будет крайне трудно, непонятно, где завершается один шаг и начинается другой. Мы можем решить эту проблему при помощи комментариев. Добавим комментарии к каждому шагу в нашем тестовом классе:
Это немного улучшит читаемость кода, но всех проблем не решит. Например, после падения одного из тестов, как мы узнаем, на каком шаге это произошло? Espresso в таком случае просто даст дамп в лог, придется исследовать записи, пытаясь понять, что пошло не так. Было бы гораздо лучше, если бы в логах отображались сведения о начале и завершении каждого шага.
Мы также можем обернуть некоторые участки кода в блок try-catch, чтобы фиксировать падение теста. Если мы это сделаем, наш тест будет выглядеть следующим образом:
В некоторых блоках catch мы создаем скриншоты, которые в будущем могли бы помочь анализировать падения. Один из способов сделать скриншот — вызвать метод takeScreenshot(), но использовать его напрямую не рекомендуется. С более удобным и гибким инструментом для создания скриншотов в Kaspresso можно познакомиться в этом уроке.
Open-source-фреймворк Kaspresso предлагает полезную и удобную абстракцию — Step. У нее внутри реализовано все то, что мы сейчас написали вручную.
Чтобы использовать step’ы, необходимо вызвать метод run {} и в фигурных скобках перечислить все шаги, которые будут выполнены во время теста. Каждый шаг нужно вызывать внутри блока step. Тогда код теста будет выглядеть следующим образом:
Давайте посмотрим в логи. Там мы увидим как логи конкретных действий и проверок, производимых во время теста, так и метаинформацию о шагах — название, вызывающий класс, время прохождения, сообщение об успехе или лог ошибки, если тест упал.
![](https://habrastorage.org/webt/nm/ra/qg/nmraqglzreqqefinfegtlgm02us.png)
Каждый пользователь фреймворка Kaspresso может добавить к функционалу step’ов необходимые ему улучшения: запись шагов в allure-отчет, скриншоты на месте падения шага, вывод иерархии view и многое другое. Для этого пользователю необходимо написать свой перехватчик или использовать один из готовых.
Запустим тест еще раз с выключенным Интернетом:
![](https://habrastorage.org/webt/mn/x0/g2/mnx0g23rjubdhq8zrbrrlu7fz-e.png)
Часто, когда мы составляем тесты, нам приходится учитывать, что проверять работу приложения необходимо в условиях определенной подготовленности. Так и в нашем тесте, перед каждым запуском требуется, чтобы устройство приходило в дефолтное состояние и было возвращено в него после прохождения теста.
Для этого в Kaspresso есть блоки before и after. Код внутри блока before будет выполняться перед тестом — здесь мы можем установить настройки по умолчанию. Во время выполнения теста состояние телефона может меняться: мы можем выключить Интернет, сменить ориентацию, но после теста нужно вернуть исходное состояние. Делать это мы будем внутри блока after.
Улучшим наш тестовый класс, используя секции. Перед началом прохождения теста в секции before устанавливаем книжную ориентацию (landscape mode) и включаем Wifi:
Блоки before и after не только помогают визуально выделить подготовку нужного состояния в отдельные блоки, но и гарантировать их выполнение. Если мы обратимся к тесту, который писали в начале статьи с использованием Espresso, то увидим, что те же действия были записаны в виде шагов, и, если тест упадет в середине выполнения, установка настроек по умолчанию может так и не оказаться вызванной. В случае применения секций before и after гарантируется выполнение этих блоков в нужные моменты.
Также заметим, что сейчас после переворота устройства мы проверяем, что текст остался прежним, но не проверяем, что ориентация действительно поменялась. Получается, что если метод device.exploit.rotate() по какой-то причине не сработал, то ориентация не меняется и проверка текста будет бесполезной. Давайте добавим проверку, что ориентация девайса стала альбомной.
Итак, подведем итоги. Open-source-фреймворк Kaspresso позволяет делать тестовый код читаемым за счет декларативного подхода с использованием Kotlin DSL и реализованным паттерном Page Object «из коробки» и стабильным за счет внутренних доработок и интерсепторов. Некоторые плюсы Kaspresso, рассмотренные нами на конкретном примере:
Если эта статья была полезна для вас и вы планируете использовать Kaspresso в своих проектах, то присоединяйтесь к сообществу Kaspresso в Телеграм. Там мы постараемся оказать вам всю необходимую поддержку и проанонсируем следующие статьи из текущего цикла материалов о Kaspresso.
Вы также можете пройти Tutorial, подготовленный командой Kaspresso, чтобы ознакомиться с другими возможностями фреймворка, и посетить раздел Wiki. Надеемся увидеть вас среди наших контрибьюторов!
И не забудьте поставить звезду на Github ;)
![](https://habrastorage.org/webt/xy/0t/uz/xy0tuzp8haa81-g7_ucocicloou.png)
Итак, когда появляется необходимость в написании автотестов на Android, мы чаще всего обращаемся к фреймворку Espresso от Google (если вдруг вы не знакомы с Espresso, подробности можно найти в официальной документации). Espresso позволяет работать с элементами приложения нативно и методом белого ящика. Нужные элементы можно сначала находить на экране с помощью matcher'ов, а затем выполнять с ними различные действия или проверки.
Рассмотрим работу автотестов на конкретном примере. Запустить приложение и воспроизвести все описанные далее шаги можно самостоятельно, скачав исходники на нашем гитхабе и запустив проект Tutorial.
При открытии приложения мы видим экран, содержащий кнопку Internet Availability, при нажатии на которую мы переходим на экран проверки состояния Wifi.
![](https://habrastorage.org/webt/_h/xm/e_/_hxme_jaipswh3soy1whwpksunc.png)
Кнопка Check Wifi status кликабельна. После клика отображается текущий статус состояния Wifi.
![](https://habrastorage.org/webt/xt/ix/f3/xtixf3pwumgyv6vrk1bdvnbolcs.png)
Нам также важно, чтобы при повороте экрана текст, указывающий статус Wifi, не менялся.
![](https://habrastorage.org/webt/me/24/vq/me24vqyh6puuunekenwcpzomewa.png)
Итого, для проверки корректности работы приложения нам необходимо пройти следующие шаги:
- Перед началом прохождения теста установить на устройстве книжную ориентацию (landscape mode), включить Wifi и запустить главный экран приложения.
- Проверить, что кнопка Internet Availability видна и кликабельна.
- Проверить, что заголовок, свидетельствующий о статусе Wifi, не содержит текст.
- Кликнуть по кнопке Check Wifi status.
- Проверить, что текст в заголовке стал enabled.
- Отключить Wifi.
- Кликнуть по кнопке Check Wifi status.
- Проверить, что текст в заголовке стал disabled.
- Перевернуть устройство.
- Проверить, что текст в заголовке сохранился disabled.
Давайте попробуем автоматизировать этот текстовый тест-кейс и на его примере обсудим имеющиеся недостатки Espresso.
Пример теста на Espresso
Наш тестовый класс с использованием фреймворка Espresso:
package com.kaspersky.kaspresso.tutorial
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isClickable
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.activityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class WifiSampleEspressoTest {
@get:Rule
val activityRule = activityScenarioRule<MainActivity>()
@Test
fun test() {
// launch target screen
onView(withId(R.id.wifi_activity_btn)).check(matches(isDisplayed()))
onView(withId(R.id.wifi_activity_btn)).check(matches(isClickable()))
onView(withId(R.id.wifi_activity_btn)).perform(ViewActions.click())
// set portrait orientation
val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
uiAutomation.executeShellCommand(SHELL_COMMAND_AUTO_ROTATE_DISABLE)
uiAutomation.executeShellCommand(SHELL_COMMAND_PORTRAIT_ORIENTATION)
// test
onView(withId(R.id.wifi_status)).check(matches(withText("")))
onView(withId(R.id.check_wifi_btn)).check(matches(isDisplayed()))
onView(withId(R.id.check_wifi_btn)).check(matches(isClickable()))
onView(withId(R.id.check_wifi_btn)).perform(ViewActions.click())
onView(withId(R.id.wifi_status)).check(matches(withText(R.string.enabled_status)))
// Turning off wifi
uiAutomation.executeShellCommand(SHELL_COMMAND_TURN_OFF_WIFI)
// wait for switching wifi
Thread.sleep(3000)
// test
onView(withId(R.id.check_wifi_btn)).perform(ViewActions.click())
onView(withId(R.id.wifi_status)).check(matches(withText(R.string.disabled_status)))
//rotate
uiAutomation.executeShellCommand(SHELL_COMMAND_AUTO_ROTATE_DISABLE)
uiAutomation.executeShellCommand(SHELL_COMMAND_LANDSCAPE_ORIENTATION)
// wait for rotation
Thread.sleep(3000)
// test
onView(withId(R.id.wifi_status)).check(matches(withText(R.string.disabled_status)))
}
private companion object {
const val SHELL_COMMAND_AUTO_ROTATE_DISABLE = "content insert --uri content://settings/system --bind name:s:accelerometer_rotation --bind value:i:0"
const val SHELL_COMMAND_PORTRAIT_ORIENTATION = "content insert --uri content://settings/system --bind name:s:user_rotation --bind value:i:0"
const val SHELL_COMMAND_LANDSCAPE_ORIENTATION = "content insert --uri content://settings/system --bind name:s:user_rotation --bind value:i:1"
const val SHELL_COMMAND_TURN_OFF_WIFI = "svc wifi disable"
}
}
В этом примере мы столкнулись с тем, что невозможно реализовать тест только средствами Espresso, поэтому нам пришлось воспользоваться библиотекой UiAutomator. Тест запускается и позволяет проверить корректность работы приложения, но, к сожалению, написанный код имеет несколько недостатков.
Что нам хотелось бы улучшить в написанном коде: разделить логику описания элементов и последовательность действий и проверок, использовать декларативный стиль написания тестов, избавиться от большого количества повторяющихся вызовов onView и иерархической вложенности. К сожалению, покрыть все потребности в автотестировании Android только с помощью Espresso невозможно из-за отсутствия определенных фич.
Улучшение кода
Попробуем переписать код нашего теста, прибегнув к помощи open-source-фреймворка Kaspresso, который использует декларативный подход к написанию тестов. Давайте рассмотрим на конкретном участке кода.
Было так:
onView(withId(R.id.wifi_activity_btn)).check(matches(isDisplayed()))
onView(withId(R.id.wifi_activity_btn)).check(matches(isClickable()))
onView(withId(R.id.wifi_activity_btn)).perform(ViewActions.click())
А станет вот так:
wifiActivityButton {
isVisible()
isClickable()
click()
}
Kaspresso использует под капотом Kakao — Kotlin-DSL-обертку над Espresso. От Kakao open-source-фреймворк Kaspresso унаследовал две основные концепции. Первая — KView — особое представление элементов интерфейса, с которыми будет происходить взаимодействие в тесте. Использование KView избавляет нас от постоянного вызова метода onView, теперь достаточно всего один раз положить matcher’ы в конструктор KView.
Вторая концепция — класс Screen (реализация паттерна Page Object), где описываются все элементы, с которыми будет происходить взаимодействие во время теста. Концепция пришла из веб-разработки, она заключается в создании описания экрана, видимого пользователю. Этот объект не содержит никакой логики. Это позволяет в отдельном файле описать скрины (Screens) и их элементы и взаимодействовать с ними из кода класса теста в декларативном стиле. Таким образом, Page Object’ы являются абсолютно независимыми, за счет чего достигается максимальная переиспользуемость.
Давайте перепишем наш тестовый класс, используя фреймворк Kaspresso. Если у вас возникнут сложности с одним из последующих шагов, вы можете найти готовый вариант автотеста в ветке tutorial_results.
Первым делом надо создать Page Object MainScreen:
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 wifiActivityButton = KButton { withId(R.id.wifi_activity_btn) }
}
Здесь мы встречаемся с необходимостью определить id макета (layoutId), который установлен на экране, и название класса (viewClass). Это требуется для связывания теста с конкретным файлом верстки и классом activity или fragment. Такое связывание сделает дальнейшую поддержку и доработку теста более удобной, но пока перед нами стоит задача рассмотреть демонстрационный тестовый класс, поэтому оставим значение null.
Далее создадим WifiScreen:
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
import io.github.kakaocup.kakao.text.KTextView
object WifiScreen : KScreen<WifiScreen>() {
override val layoutId: Int? = null
override val viewClass: Class<*>? = null
val checkWifiButton = KButton { withId(R.id.check_wifi_btn) }
val wifiStatus = KTextView { withId(R.id.wifi_status) }
}
Теперь займемся тест-кейсами. Класс тестов должен быть унаследован от класса TestCase. Создаем такой класс WifiSampleTest и добавляем метод, помеченный аннотацией Test (import org.junit.Test), в котором будем проверять работу приложения.
package com.kaspersky.kaspresso.tutorial
import androidx.test.ext.junit.rules.activityScenarioRule
import com.kaspersky.kaspresso.device.exploit.Exploit
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import com.kaspersky.kaspresso.tutorial.screen.MainScreen
import com.kaspersky.kaspresso.tutorial.screen.WifiScreen
import org.junit.Rule
import org.junit.Test
class WifiSampleTest : TestCase() {
@get:Rule
val activityRule = activityScenarioRule<MainActivity>()
@Test
fun test() {
MainScreen {
wifiActivityButton {
isVisible()
isClickable()
click()
}
}
WifiScreen { device.exploit.setOrientation(Exploit.DeviceOrientation.Portrait)
wifiStatus.hasEmptyText()
checkWifiButton {
isVisible()
isClickable()
click()
}
wifiStatus.hasText(R.string.enabled_status)
device.network.toggleWiFi(false)
checkWifiButton.click()
wifiStatus.hasText(R.string.disabled_status)
device.exploit.rotate()
wifiStatus.hasText(R.string.disabled_status)
}
}
}
Ранее мы упоминали, что Espresso не умеет работать с Android OS, вследствие чего пользователю приходится использовать библиотеку UiAutomator. Обратите внимание, что в коде теста используется экземпляр класса Device, являющегося частью фреймворка Kaspresso. У этого объекта есть множество полезных методов, подходящих для взаимодействия с Android OS. Подробнее про них вы можете почитать здесь.
Что мы получили?
- Разграничение описания экранов и проверки логики их работы — тест-кейсы теперь могут быть описаны независимо от экранов, а сами классы скринов могут быть переиспользованы в разных тестах.
- Код стал гораздо более читаемым, и появилась возможность писать тесты в декларативном стиле — просто указываем, на каком скрине какие действия и проверки мы хотим выполнить.
Разделение кода в соответствии с шагами тест-кейсов
Код тестового класса уже стал выглядеть лучше, но в нем все еще присутствуют проблемные места. Обычно любые тесты (в том числе ручные) выполняются по тест-кейсам. То есть у тестировщика есть последовательность шагов, которые он выполняет для проверки работоспособности экрана. Теперь представим, что наш тестовый класс содержит в несколько раз больше шагов и, соответственно, строк кода. Разобраться и добавить необходимые строчки в таком случае будет крайне трудно, непонятно, где завершается один шаг и начинается другой. Мы можем решить эту проблему при помощи комментариев. Добавим комментарии к каждому шагу в нашем тестовом классе:
package com.kaspersky.kaspresso.tutorial
import androidx.test.ext.junit.rules.activityScenarioRule
import com.kaspersky.kaspresso.device.exploit.Exploit
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import com.kaspersky.kaspresso.tutorial.screen.MainScreen
import com.kaspersky.kaspresso.tutorial.screen.WifiScreen
import org.junit.Rule
import org.junit.Test
class WifiSampleWithStepsTest : TestCase() {
@get:Rule
val activityRule = activityScenarioRule<MainActivity>()
@Test
fun test() {
// Step 1. Open target screen
MainScreen {
wifiActivityButton {
isVisible()
isClickable()
click()
}
}
WifiScreen {
// Step 2. Check correct wifi status
device.exploit.setOrientation(Exploit.DeviceOrientation.Portrait)
wifiStatus.hasEmptyText()
checkWifiButton {
isVisible()
isClickable()
click()
}
wifiStatus.hasText(R.string.enabled_status)
device.network.toggleWiFi(false)
checkWifiButton.click()
wifiStatus.hasText(R.string.disabled_status)
// Step 3. Rotate device and check wifi status
device.exploit.rotate()
wifiStatus.hasText(R.string.disabled_status)
}
}
}
Это немного улучшит читаемость кода, но всех проблем не решит. Например, после падения одного из тестов, как мы узнаем, на каком шаге это произошло? Espresso в таком случае просто даст дамп в лог, придется исследовать записи, пытаясь понять, что пошло не так. Было бы гораздо лучше, если бы в логах отображались сведения о начале и завершении каждого шага.
Мы также можем обернуть некоторые участки кода в блок try-catch, чтобы фиксировать падение теста. Если мы это сделаем, наш тест будет выглядеть следующим образом:
package com.kaspersky.kaspresso.tutorial
import android.util.Log
import androidx.test.core.app.takeScreenshot
import androidx.test.ext.junit.rules.activityScenarioRule
import com.kaspersky.kaspresso.device.exploit.Exploit
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import com.kaspersky.kaspresso.tutorial.screen.MainScreen
import com.kaspersky.kaspresso.tutorial.screen.WifiScreen
import org.junit.Rule
import org.junit.Test
class WifiSampleWithStepsTest : TestCase() {
@get:Rule
val activityRule = activityScenarioRule<MainActivity>()
@Test
fun test() {
try {
Log.i("KASPRESSO", "Step 1. Open target screen -> started")
MainScreen {
wifiActivityButton {
isVisible()
isClickable()
click()
}
}
Log.i("KASPRESSO", "Step 1. Open target screen -> succeed")
} catch (e: Throwable) {
Log.i("KASPRESSO", "Step 1. Open target screen -> failed")
takeScreenshot()
}
WifiScreen {
try {
Log.i("KASPRESSO", "Step 2. Check correct wifi status -> started")
device.exploit.setOrientation(Exploit.DeviceOrientation.Portrait)
wifiStatus.hasEmptyText()
checkWifiButton {
isVisible()
isClickable()
click()
}
wifiStatus.hasText(R.string.enabled_status)
device.network.toggleWiFi(false)
checkWifiButton.click()
wifiStatus.hasText(R.string.disabled_status)
Log.i("KASPRESSO", "Step 2. Check correct wifi status -> succeed")
} catch (e: Throwable) {
Log.i("KASPRESSO", "Step 2. Check correct wifi status -> failed")
}
try {
Log.i("KASPRESSO", "Step 3. Rotate device and check wifi status -> started")
device.exploit.rotate()
wifiStatus.hasText(R.string.disabled_status)
Log.i("KASPRESSO", "Step 3. Rotate device and check wifi status -> succeed")
} catch (e: Throwable) {
Log.i("KASPRESSO", "Step 3. Rotate device and check wifi status -> failed")
takeScreenshot()
}
}
}
}
В некоторых блоках catch мы создаем скриншоты, которые в будущем могли бы помочь анализировать падения. Один из способов сделать скриншот — вызвать метод takeScreenshot(), но использовать его напрямую не рекомендуется. С более удобным и гибким инструментом для создания скриншотов в Kaspresso можно познакомиться в этом уроке.
Open-source-фреймворк Kaspresso предлагает полезную и удобную абстракцию — Step. У нее внутри реализовано все то, что мы сейчас написали вручную.
Чтобы использовать step’ы, необходимо вызвать метод run {} и в фигурных скобках перечислить все шаги, которые будут выполнены во время теста. Каждый шаг нужно вызывать внутри блока step. Тогда код теста будет выглядеть следующим образом:
package com.kaspersky.kaspresso.tutorial
import androidx.test.ext.junit.rules.activityScenarioRule
import com.kaspersky.kaspresso.device.exploit.Exploit
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import com.kaspersky.kaspresso.tutorial.screen.MainScreen
import com.kaspersky.kaspresso.tutorial.screen.WifiScreen
import org.junit.Rule
import org.junit.Test
class WifiSampleWithStepsTest : TestCase() {
@get:Rule
val activityRule = activityScenarioRule<MainActivity>()
@Test
fun test() {
run {
step("Open target screen") {
MainScreen {
wifiActivityButton {
isVisible()
isClickable()
click()
}
}
}
step("Check correct wifi status") {
WifiScreen {
device.exploit.setOrientation(Exploit.DeviceOrientation.Portrait)
wifiStatus.hasEmptyText()
checkWifiButton {
isVisible()
isClickable()
click()
}
wifiStatus.hasText(R.string.enabled_status)
device.network.toggleWiFi(false)
checkWifiButton.click()
wifiStatus.hasText(R.string.disabled_status)
}
}
step("Rotate device and check wifi status") {
WifiScreen {
device.exploit.rotate()
wifiStatus.hasText(R.string.disabled_status)
}
}
}
}
}
Давайте посмотрим в логи. Там мы увидим как логи конкретных действий и проверок, производимых во время теста, так и метаинформацию о шагах — название, вызывающий класс, время прохождения, сообщение об успехе или лог ошибки, если тест упал.
![](https://habrastorage.org/webt/nm/ra/qg/nmraqglzreqqefinfegtlgm02us.png)
Каждый пользователь фреймворка Kaspresso может добавить к функционалу step’ов необходимые ему улучшения: запись шагов в allure-отчет, скриншоты на месте падения шага, вывод иерархии view и многое другое. Для этого пользователю необходимо написать свой перехватчик или использовать один из готовых.
Запустим тест еще раз с выключенным Интернетом:
![](https://habrastorage.org/webt/mn/x0/g2/mnx0g23rjubdhq8zrbrrlu7fz-e.png)
Sections — подготовка состояний до и после теста
Часто, когда мы составляем тесты, нам приходится учитывать, что проверять работу приложения необходимо в условиях определенной подготовленности. Так и в нашем тесте, перед каждым запуском требуется, чтобы устройство приходило в дефолтное состояние и было возвращено в него после прохождения теста.
Для этого в Kaspresso есть блоки before и after. Код внутри блока before будет выполняться перед тестом — здесь мы можем установить настройки по умолчанию. Во время выполнения теста состояние телефона может меняться: мы можем выключить Интернет, сменить ориентацию, но после теста нужно вернуть исходное состояние. Делать это мы будем внутри блока after.
Улучшим наш тестовый класс, используя секции. Перед началом прохождения теста в секции before устанавливаем книжную ориентацию (landscape mode) и включаем Wifi:
device.exploit.setOrientation(Exploit.DeviceOrientation.Portrait) device.network.toggleWiFi(true)
Блоки before и after не только помогают визуально выделить подготовку нужного состояния в отдельные блоки, но и гарантировать их выполнение. Если мы обратимся к тесту, который писали в начале статьи с использованием Espresso, то увидим, что те же действия были записаны в виде шагов, и, если тест упадет в середине выполнения, установка настроек по умолчанию может так и не оказаться вызванной. В случае применения секций before и after гарантируется выполнение этих блоков в нужные моменты.
Также заметим, что сейчас после переворота устройства мы проверяем, что текст остался прежним, но не проверяем, что ориентация действительно поменялась. Получается, что если метод device.exploit.rotate() по какой-то причине не сработал, то ориентация не меняется и проверка текста будет бесполезной. Давайте добавим проверку, что ориентация девайса стала альбомной.
Assert.assertTrue(device.context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
package com.kaspersky.kaspresso.tutorial
import android.content.res.Configuration
import androidx.test.ext.junit.rules.activityScenarioRule
import com.kaspersky.kaspresso.device.exploit.Exploit
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import com.kaspersky.kaspresso.tutorial.screen.MainScreen
import com.kaspersky.kaspresso.tutorial.screen.WifiScreen
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
class WifiSampleWithStepsTest : TestCase() {
@get:Rule
val activityRule = activityScenarioRule<MainActivity>()
@Test
fun test() {
before {
device.exploit.setOrientation(Exploit.DeviceOrientation.Portrait)
device.network.toggleWiFi(true)
}.after {
device.exploit.setOrientation(Exploit.DeviceOrientation.Portrait)
device.network.toggleWiFi(true)
}.run {
step("Open target screen") {
MainScreen {
wifiActivityButton {
isVisible()
isClickable()
click()
}
}
}
step("Check correct wifi status") {
WifiScreen {
wifiStatus.hasEmptyText()
checkWifiButton {
isVisible()
isClickable()
click()
}
wifiStatus.hasText(R.string.enabled_status)
device.network.toggleWiFi(false)
checkWifiButton.click()
wifiStatus.hasText(R.string.disabled_status)
}
}
step("Rotate device and check wifi status") {
WifiScreen {
device.exploit.rotate()
Assert.assertTrue(device.context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
wifiStatus.hasText(R.string.disabled_status)
}
}
}
}
}
Заключение и полезные ссылки
Итак, подведем итоги. Open-source-фреймворк Kaspresso позволяет делать тестовый код читаемым за счет декларативного подхода с использованием Kotlin DSL и реализованным паттерном Page Object «из коробки» и стабильным за счет внутренних доработок и интерсепторов. Некоторые плюсы Kaspresso, рассмотренные нами на конкретном примере:
- Хорошая читаемость — больше не нужно использовать длинные конструкции с matcher’ами для поиска элементов на экране для взаимодействия из теста, и все элементы интерфейса, с которыми будет взаимодействовать ваш тест, могут быть описаны в одном месте — в конкретном объекте Screen.
- Логирование — Kaspresso предоставляет развернутые и понятные логи с указанием текущего шага. Вам не нужно их добавлять вручную — все реализовано внутри. В случае необходимости вы можете изменять и дополнять логи по своему желанию.
- Архитектура кода — используя описанную выше реализацию паттерна Page Object, вы можете сделать свой код в тестовых файлах более читабельным, удобным для дальнейшей поддержки, повторно используемым и понятным. Kaspresso также предоставляет различные методы и абстракции для улучшения архитектуры (такие как step, секции before и after и многое другое).
- Низкий порог входа в освоение фреймворка — для написания автотестов не требуется знания языка программирования, достаточно использовать декларативный подход.
Если эта статья была полезна для вас и вы планируете использовать Kaspresso в своих проектах, то присоединяйтесь к сообществу Kaspresso в Телеграм. Там мы постараемся оказать вам всю необходимую поддержку и проанонсируем следующие статьи из текущего цикла материалов о Kaspresso.
Вы также можете пройти Tutorial, подготовленный командой Kaspresso, чтобы ознакомиться с другими возможностями фреймворка, и посетить раздел Wiki. Надеемся увидеть вас среди наших контрибьюторов!
И не забудьте поставить звезду на Github ;)
AlexVWill
Если вы считаете свою работу бесполезной, вспомните о том, что есть разработчики приложений Kaspersky под Android.