Подробное руководство по тестированию в Android

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

Интеграционные тесты находятся поверх модульных тестов. Они используются для проверки совместной работы двух классов или компонентов. Классы могут быть любыми, например нажатием кнопки фильтра, чтобы отобразить раздел фильтра на экране. В отличие от модульных тестов, интеграционные тесты включают компоненты Android, поэтому для их выполнения нам потребуется физическое устройство или эмулятор.

Использование компонентов Android немного усложняет настройку интеграционных тестов, таких как запуск пользовательских тестов, настройка теста рукоятки и т. д. Мы рассмотрим их все шаг за шагом в следующих разделах:

Настройка рукояти

Хватит теории. Начнем с настройки внедрения зависимостей для написания интеграционных тестов.

HiltTestRunner

Поскольку мы используем библиотеку Hilt для внедрения зависимостей, нам нужно создать собственный AndroidJUnitRunner, который использует HiltTestApplication вместо обычного className.

Создайте собственный класс Kotlin HiltTestRunner в корневом пакете внутриandroidTest. Расширьте класс с помощью AndroidJUnitRunner и переопределите newApplication. В операторе return замените className на HiltTestApplication имя класса. Взгляни:

Далее нам нужно настроить HiltTestRunner в файле gradle под узлом defaultConfig. Взгляни:

ТестАппМодуле

Теперь, когда мы настроили HiltTestRunner, следующим шагом будет создание TestAppModule, тестовой версии реальной рукояти AppModule, где мы настроим зависимости, такие как база данных Room, репозиторий, варианты использования и т. д.

Вопрос в том, зачем нам TestAppModule? Ответ прост: нам не нужна реальная база данных или реальные вызовы API в тестах. Допустим, в TestAppModule мы можем вернуть базу данных комнаты в памяти, что быстрее. Чтобы иметь возможность использовать поддельные или легковесные источники данных в наших тестах, нам нужно TestAppModule.

Примечание. Тесты будут выполняться, даже если вы используете настоящий Hilt AppModule без каких-либо ошибок.

Теперь, когда мы знаем, зачем нам нужно создать TestAppModule, давайте начнем с части кодирования. В нашем случае то же самое, за исключением базы данных Room в памяти. Взгляни:

Создайте тестовый пример и объявите правила

Следующим шагом является создание тестового примера. Давайте выберем изменение видимости раздела фильтра. Взгляни:

Что происходит внутри, так это то, что мы вызываем функцию onEvent в классе ToDoListViewmode с событием TasksEvent.ToggleFilterSection, где мы обновим состояние, которое наблюдается в функции TasksScreen compose, чтобы переключить видимость раздела фильтра. Итак, мы тестируем взаимодействие двух классов.

Хватит теории. Давайте создадим тестовый пример; Это похоже на то, как мы создавали модульный тест в части 1. Сначала перейдите к этому классу, затем поместите курсор на имя класса и нажмите control + return (macOS). Это вызовет всплывающее окно, позволяющее нам выбрать опцию Test. Взгляни:

Это приведет вас к всплывающему окну создания теста, где мы можем настроить имя, путь, библиотеку тестирования и т. д. Посмотрите:

Если вы наблюдаете пакет Destination, который представляет собой не что иное, как путь, по которому будет создан тестовый пример, Android Studio создает тестовый пример в тестовом пакете Android с той же структурой пакета, что и основной пакет. Когда вы закончите, нажмите кнопку «ОК».

Далее нам нужно аннотировать класс с помощью HiltAndroidTest и @UninstallModules(AppModule::class)..

  • HiltAndroidTest: аннотация, используемая для маркировки тестов эмулятора Android, требующих внедрения.
  • UninstallModules(AppModule::class): По сути, AppModule и TestAppModule выставляют одни и те же зависимости, это вызовет конфликты, поэтому мы советуем не использовать здесь класс AppModule.

Взгляни:

@HiltAndroidTest
@UninstallModules(AppModule::class)
class TasksScreenTest {
}

Создание правил

Здесь нам нужны два правила: HiltAndroidRule и createAndroidComposeRule.

  • HiltAndroidRule: TestRule для Hilt можно использовать с тестами JVM или Instrumentation. Это правило является обязательным. Компонент Dagger component не будет создан без этого тестового правила.
  • createAndroidComposeRule: фабричный метод, обеспечивающий специфичную для Android реализацию createComposeRule для данного класса действий. createComposeRule используется для проверки функции составления.

Нам нужно убедиться, что HiltAndroidRule выполняется до createAndroidComposeRule, поэтому нам нужно указать порядок выполнения, как показано ниже:

Затем нам нужно написать функцию настройки, в которой нам нужно вызвать функцию внедрения в правиле рукояти (завершает введение кинжала). Это должно быть сделано перед выполнением любых тестов. Взгляните на код:

Вот и все. Мы закончили с рукояткой и настройкой компоновки для интеграционных тестов.

Изучение идентификации узла с помощью правила создания

Прежде чем приступить к написанию фактического тестового примера, нам нужно изучить еще одну концепцию, не что иное, как получение ссылки на представления на экране, подобные findViewById.

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

Для метрик мы можем найти представления на основе отображаемого текста, например, для текстовых полей, или мы можем использовать contentDescription для значков, или мы можем добавить testtag, чтобы найти любое составное представление и использовать его в нашем тестировании.

Для подхода compose из коробки предлагает такие функции, как onNodeWithTag, onNodeWithContentDescription и onNodeWithText, чтобы найти единственную ссылку представления, которая соответствует критериям. Мы также можем найти несколько представлений с функциями onAllNodesWithTag, onAllNodesWithContentDescription и onAllNodesWithText. Мы должны использовать вышеуказанные функции с composeRule, посмотрите:

composeRule.onNodeWithTag(TestTags.FILTER_BUTTON).assertExists()
composeRule.onNodeWithText("Filter").assertExists()
composeRule.onNodeWithContentDescription("Filter").assertExists()

В этой серии статей я использую утверждения утверждений из библиотеки правды Google. Мы уже импортировали библиотеку в части 1 этой серии.

Небольшое примечание: добавьте объектный класс TestTags Kotlin, чтобы объявить все константы тестовых тегов в следующей структуре пакета в каталоге main.

Первый интеграционный тест

Начнем с наименования. Это не обязательно, но я предпочитаю использовать действие и результат как часть имени функции, разделенного _. Взгляни:

@Test
fun clickToggleFilterSection_isVisible(){
}

Мы будем использовать четыре функции для завершения теста:

  1. assertDoesNotExist: используется, чтобы убедиться, что представление не находится в иерархии представлений, являющейся частью библиотеки правды Google.
  2. performClick: используется для выполнения щелчка по соответствующему представлению, входящему в состав библиотеки тестов пользовательского интерфейса.
  3. assertExists: используется, чтобы убедиться, что представление существует в иерархии представлений, являющейся частью библиотеки правды Google.
  4. assertIsDisplayed: используется, чтобы убедиться, что соответствующее представление видно пользователю, часть библиотеки тестов пользовательского интерфейса.

Теперь взгляните на тестовый пример:

Давайте запустим тестовый пример, как показано ниже:

Примечание. Добавлено Thread.sleep, чтобы отчетливо было видно выполнение теста, пожалуйста, не добавляйте их в режиме реального времени, если это не требуется.

Интеграционный тест между двумя экранами

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

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

Теперь давайте запустим тестовый пример. Взгляни:

Интеграционные тесты для проверки случая ошибки

Завершим статью еще одним интеграционным тестом. Это связано с обработкой исключений. Мы рассмотрим возможность создания функции задачи для этого. Если вы видите вариант использования «Добавить задачу», он выдаст исключение, если пользователь попытается добавить задачу без действительного заголовка. Взгляните на вариант использования:

Написание этого тестового примера включает три шага:

  1. Мы начнем с перехода к экрану добавления задачи, нажав на потрясающую кнопку.
  2. Затем мы нажмем кнопку «Сохранить», не имея действительного заголовка.
  3. Наконец, мы проверим, отображается ли пользователю snackbar с сообщением об исключении.

Теперь давайте запустим вариант использования:

Еще несколько интеграционных тестов вы можете найти в моем open-source проекте. Не стесняйтесь клонировать проект и экспериментировать. Пожалуйста, убедитесь, что вы находитесь на ветке feature/integration_tests .

Что дальше?

Теперь, когда мы завершили интеграционные тесты, следующим шагом будет тестирование таких функций, как добавление задачи, удаление задачи и т. д., с помощью сквозных тестов. Оставайтесь с нами, чтобы узнать больше.