Основы тестирования Android-приложений

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

Преимущества тестирования

Тестирование — неотъемлемая часть процесса разработки приложения. Последовательно выполняя тесты вашего приложения, вы можете проверить его корректность, функциональное поведение и удобство использования, прежде чем выпустить его публично.

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

Однако ручное тестирование плохо масштабируется, и можно легко не заметить регрессии в поведении вашего приложения. Автоматизированное тестирование предполагает использование инструментов, которые выполняют тесты за вас. Это быстрее, более воспроизводимо и, как правило, дает вам более полезную обратную связь о вашем приложении на ранних этапах процесса разработки.

Виды тестов в Android

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

Предмет

Например, в зависимости от предмета существуют разные типы тестов:

  • Функциональное тестирование : делает ли мое приложение то, что должно?
  • Тестирование производительности : делается ли оно быстро и эффективно?
  • Тестирование доступности : хорошо ли оно работает со службами доступности?
  • Тестирование совместимости : хорошо ли оно работает на каждом устройстве и уровне API?

Объем

Тесты также различаются в зависимости от размера или степени изоляции :

  • Модульные тесты или небольшие тесты проверяют только очень небольшую часть приложения, например метод или класс.
  • Сквозные тесты или большие тесты проверяют одновременно большие части приложения, например весь экран или пользовательский поток.
  • Средние тесты находятся между ними и проверяют интеграцию между двумя или более модулями.
Тесты могут быть маленькими, средними и большими.
Рис. 1. Области тестирования в типичном приложении.

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

Инструментальные и локальные тесты

Вы можете запускать тесты на Android-устройстве или на другом компьютере:

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

Не все модульные тесты являются локальными, и не все сквозные тесты выполняются на устройстве. Например:

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

Примеры

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

Эспрессо

// When the Continue button is clicked
onView(withText("Continue"))
    .perform(click())

// Then the Welcome screen is displayed
onView(withText("Welcome"))
    .check(matches(isDisplayed()))

Создать интерфейс

// When the Continue button is clicked
composeTestRule.onNodeWithText("Continue").performClick()

// Then the Welcome screen is displayed
composeTestRule.onNodeWithText("Welcome").assertIsDisplayed()

В этом фрагменте показана часть модульного теста для ViewModel (локальный тест на стороне хоста):

// Given an instance of MyViewModel
val viewModel = MyViewModel(myFakeDataRepository)

// When data is loaded
viewModel.loadData()

// Then it should be exposing data
assertTrue(viewModel.data != null)

Определение стратегии тестирования

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

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

Ненадежные тесты

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

Тестируемая архитектура

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

Архитектура, которая не поддается тестированию, приводит к следующему:

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

Дополнительные сведения о рекомендациях по архитектуре см. в руководстве по архитектуре приложений .

Подходы к развязке

Если вы можете извлечь часть функции, класса или модуля из остальных, тестирование будет проще и эффективнее. Эта практика известна как развязка, и это концепция, наиболее важная для тестируемой архитектуры.

Общие методы развязки включают в себя следующее:

  • Разделите приложение на слои, такие как «Презентация», «Домен» и «Данные». Вы также можете разделить приложение на модули , по одному на каждую функцию.
  • Избегайте добавления логики к сущностям, имеющим большие зависимости, таким как действия и фрагменты. Используйте эти классы в качестве точек входа в платформу и перемещайте пользовательский интерфейс и бизнес-логику в другое место, например, на составной уровень, ViewModel или уровень домена.
  • Избегайте прямых зависимостей платформы в классах, содержащих бизнес-логику. Например, не используйте контексты Android в ViewModels .
  • Сделайте зависимости легко заменяемыми . Например, используйте интерфейсы вместо конкретных реализаций. Используйте внедрение зависимостей, даже если вы не используете платформу DI.

Следующие шаги

Теперь, когда вы знаете, почему вам следует тестировать и какие два основных типа тестов вы знаете, вы можете прочитать «Что тестировать» .

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