Используйте тестовые двойники в Android

При разработке стратегии тестирования элемента или системы необходимо учитывать три связанных аспекта тестирования:

  • Область применения : какую часть кода затрагивает тест? Тесты могут проверять один метод, все приложение или что-то среднее между ними. Тестируемая область находится в стадии тестирования , и ее обычно называют «испытуемым объектом» , хотя также «тестируемой системой» или «тестируемым блоком» .
  • Скорость : как быстро выполняется тест? Скорость тестирования может варьироваться от миллисекунд до нескольких минут.
  • Fidelity : Насколько «реальным» является тест? Например, если часть тестируемого кода должна выполнить сетевой запрос, действительно ли тестовый код выполняет этот сетевой запрос или он подделывает результат? Если тест действительно взаимодействует с сетью, это означает, что он имеет более высокую точность. Компромисс заключается в том, что выполнение теста может занять больше времени, может привести к ошибкам в случае сбоя сети или может оказаться дорогостоящим в использовании.

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

Изоляция и зависимости

Когда вы тестируете элемент или систему элементов, вы делаете это изолированно . Например, чтобы протестировать ViewModel, вам не нужно запускать эмулятор и пользовательский интерфейс, поскольку он не зависит (или не должен) зависеть от платформы Android.

Однако работа испытуемого может зависеть от других. Например, работа ViewModel может зависеть от хранилища данных.

Когда вам необходимо предоставить зависимость тестируемому объекту, обычной практикой является создание тестового двойника (или тестового объекта ). Тестовые двойники — это объекты, которые выглядят и действуют как компоненты вашего приложения, но создаются в вашем тесте для обеспечения определенного поведения или данных. Основные преимущества заключаются в том, что они делают ваши тесты быстрее и проще.

Виды тестовых двойников

Существуют различные типы тестовых двойников:

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

Пример: база данных в памяти.

Подделки не требуют создания макета и имеют небольшой вес. Они предпочтительнее .

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

Пример. Убедитесь, что метод в базе данных был вызван ровно один раз.

Заглушка Тестовый двойник, который ведет себя так, как вы его запрограммировали, но не имеет ожиданий относительно его взаимодействия. Обычно создается с помощью насмешливой структуры. Подделки предпочтительнее заглушек из-за простоты.
Дурачок Тестовый дубль, который передается, но не используется, например, если вам просто нужно предоставить его в качестве параметра.

Пример: пустая функция, переданная как обратный вызов щелчка.

Шпион Обертка над реальным объектом, которая также отслеживает некоторую дополнительную информацию, похожую на макеты. Их обычно избегают из-за усложнения. Поэтому фальшивки или издевательства предпочтительнее шпионов.
Тень Подделка, используемая в Робоэлектрике.

Пример использования подделки

Предположим, вы хотите выполнить модульное тестирование ViewModel, которая зависит от интерфейса UserRepository и отображает имя первого пользователя в пользовательском интерфейсе. Вы можете создать поддельный тестовый дубль, реализовав интерфейс и вернув известные данные.

object FakeUserRepository : UserRepository {
    fun getUsers() = listOf(UserAlice, UserBob)
}

val const UserAlice = User("Alice")
val const UserBob = User("Bob")

Этот поддельный UserRepository не должен зависеть от локальных и удаленных источников данных, которые будет использовать производственная версия. Файл находится в тестовом наборе исходного кода и не будет поставляться с рабочим приложением.

Ложная зависимость может возвращать известные данные независимо от удаленных источников данных.
Рисунок 1. Поддельная зависимость в модульном тесте.

Следующий тест проверяет, что ViewModel правильно предоставляет представлению первое имя пользователя.

@Test
fun viewModelA_loadsUsers_showsFirstUser() {
    // Given a VM using fake data
    val viewModel = ViewModelA(FakeUserRepository) // Kicks off data load on init

    // Verify that the exposed data is correct
    assertEquals(viewModel.firstUserName, UserAlice.name)
}

Заменить UserRepository поддельным в модульном тесте легко, поскольку ViewModel создается тестировщиком. Однако замена произвольных элементов в более крупных тестах может оказаться сложной задачей.

Замена компонентов и внедрение зависимостей

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

Даже большие сквозные тесты могут выиграть от использования двойников тестов, таких как инструментальный тест пользовательского интерфейса, который проходит через весь поток пользователей в вашем приложении. В этом случае вы можете сделать свой тест герметичным . Герметичный тест позволяет избежать всех внешних зависимостей, таких как получение данных из Интернета. Это повышает надежность и производительность.

Рисунок 2. Большой тест, охватывающий большую часть приложения и подделывающий удаленные данные.

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

Робоэлектрик

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

Robolectric — это симулятор, поэтому он не должен заменять простые модульные тесты или использоваться для тестирования совместимости. Это обеспечивает скорость и снижает стоимость за счет снижения точности в некоторых случаях. Хороший подход к тестам пользовательского интерфейса — сделать их совместимыми как с Robolectric, так и с инструментальными тестами, а также решить, когда их запускать, в зависимости от необходимости тестирования функциональности или совместимости. На Robolectric можно запускать тесты Espresso и Compose.