Понимание и освоение тестирования Android-приложений: часть 1

Понимание и освоение тестирования Android-приложений: часть 1

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

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

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

Зачем беспокоиться о тестировании?

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

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

Уверенность в вашем коде

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

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

Избавление от ошибок регрессии

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

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

Написание минималистичного кода

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

Предположим, что вы следуете Test-Driven Development (TDD), и написали тест. Теперь вашей основной целью будет написание только нужного кода, чтобы пройти этот конкретный тест. Вы не будете писать лишний или ложный код, который вы написали бы в противном случае.

В конечном итоге вы будете экономить время и быстрее выпускать безупречные и надежные функции.

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

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

Юнит-тестирование

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

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

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

Интеграционные тесты

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

Эти тесты обычно проверяют, работают ли два разных модуля или компонента вместе так, как должны. Это сильно отличается от тестирования бизнес-логики, о котором мы говорили ранее. Как правило, эти тесты являются средними по размеру (больше, чем юнит-тесты, но короче, чем end-to-end тесты) и должны составлять около 20% от общего количества тестов, написанных для вашего приложения.

Простым примером интеграционного теста будет проверка того, что нажатие на определенную кнопку в Activity приведет вас к другому действию. Это не тест единицы логики, находящейся в определенной Activity, а скорее проверка интеграции между двумя отдельными действиями, работающими вместе.

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

End-to-end тестирование

Предположительно, вы будете использовать лишь несколько таких тестов. В идеале вы должны постараться сделать так, чтобы около 10% ваших тестов были end-to-end вариантами. Как следует из названия, вы будете использовать эти тесты для полного тестирования потока – с начала и до конца.

Предположим, ваше приложение имеет регистрацию, состоящую из нескольких шагов, и вы хотите протестировать весь поток – тогда вам помогут именно end-to-end тесты. Они также будут выполняться на реальном устройстве или эмуляторе, как и тесты интеграции, и, следовательно, будут довольно медленными в исполнении.

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

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

Типы тестирования на Android

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

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

Локальные unit-тесты

Это те тесты, которые могут выполняться локально на вашей девелоперской машине. Эти тесты находятся в module/src/test/java и не имеют никакого доступа к любым API-интерфейсам Android.

Предположим, что у вас есть метод с именем validateEmailAddress(String), и вы хотите проверить его работоспособность. Вы могли бы написать очень маленькие и целенаправленные тесты для каждого сценария этой функции, например, когда электронное письмо является нулевым, пустым или недействительным.

Для модульного тестирования мы используем JUnit, который является очень популярным фреймворком юнит-тестирования, помогая нам писать четкие и понятные тесты. Мы также можем использовать Robolectric, который является еще одной популярной платформой тестирования, позволяющей нам также тестировать зависимости Android.

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

Это те тесты, которые относятся к категории тестов «Integration» и «End-to-End». Все эти тесты находятся в module /src/androidTest/java.

Обратите внимание на добавление префикса «android» перед «тестом», означающим, что эти тесты должны запускаться на устройстве Android или эмуляторе и иметь доступ к зависимостям Android. Они в основном известны как «source sets», если вам интересно.

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

Теперь у вас может возникнуть вопрос, почему эти тесты называются «инструментальными»? Это связано с тем, что они используют Android Instrumentation API через InstrumentationRegistry для управления компонентами Android и их жизненными циклами вручную, вместо того, чтобы позволять системе их контролировать.

Наиболее популярным фреймворком, который мы используем для написания инструментальных тестов, является Espresso. Но мы также можем использовать другие фреймворки, такие как UIAutomator, для написания тестов, которые охватывают несколько приложений одновременно, на что Espresso не способен.

Например, предположим, что вы хотите проверить, можете ли поделиться определенной фотографией из своего приложения с другим сторонним приложением. Здесь мы хотим проверить взаимодействие нашего приложения с другими сторонними приложениями, в которых UIAutomator может вступить в игру.

Выводы

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

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

📎📎📎📎📎📎📎📎📎📎