В первой части этой серии мы прошлись по теории, лежащей в основе использования тестовых дублеров. Теперь же давайте на примерах рассмотрим, как их можно использовать в среде Android. Создавать тестовых дублеров на Android вручную — дело не хитрое. Вам просто нужно правильно реализовать их в соответствии с контрактом реальной зависимости (как в большинстве платформ или языков) и заменить реальный взаимодействующий объект фиктивным. Помочь нам в этом могут различные техники внедрения зависимостей. Отличия от других технологий начинают появляться, когда речь заходит об инструментах. Для создания наших дублеров в среде Android мы можем использовать фреймворки, совместимые с языками Java и Kotlin. Среди самых известных можно отметить Mockito (для Java) и MockK (для Kotlin). Ниже приведены несколько примеров использования MockK: Создание стаба с помощью Mockk Создание мока с помощью Mockk Как упоминалось в предыдущей статье, все тестовые дублеры можно создавать вручную или с помощью внешних инструментов. Имейте в виду, что когда мы используем для этой цели внешние инструменты, такие как MockK или Mockito, хоть их проще настроить изначально, они, как правило, связывают наш тестовый код с деталями реализации из зависимостей. Я думаю, это следует объяснить поподробнее. Теория гласит, что если мы тестируем какое-то поведение, то любая модификация, не меняющая этого поведения (скажем, рефакторинг), не должна ломать тестовый код. Создание стаба с помощью Mockk В приведенном выше примере рефакторинг NoteApi, а именно изменение метода fetchAllNotes() с целью получения нового параметра, не должно ломать NoteRepository… но, к сожалению, приведенный выше пример сломается, так как тест использует NoteApi как часть конфигурации MockK (строка 6). Если вы представите себе несколько тестов, использующих одну и ту же конфигурацию в нескольких частях кода, у нас потенциально может возникнуть несколько поломанных рефакторингом одного класса тестов, что неизменно приводит к лишним накладным расходам и затратам на обслуживание для каждого рефакторинга. С другой стороны у нас есть дублеры, настроенные вручную, или дублеры, которые используют тот же контракт, что и реальный класс, для настройки. У них есть недостаток, заключающийся в потенциальном увеличении изначального размера кодовой базы, но также есть преимущество в том, что они не пропускают (leak) детали реализации в тесты, поскольку они не зависят от какой-либо инструментной магии. В долгосрочной перспективе это потенциально улучшает поддерживаемость. Ниже приведено сравнение того, как различные типы конфигурации дублеров могут повлиять на удобство сопровождения вашей кодовой базы: Исходя из вышеизложенного, для больших кодовых баз я рекомендую избегать создания ваших двойников с помощью инструментов, которые требуют от вас делиться с тестами какими-либо деталями реализации. Это достаточно контринтуитивно, так как вы можете подумать, что заново изобретаете колесо, не используя эти инструменты. Однако каждый новый фреймворк/библиотека, добавляемые в ваш проект, будут неминуемо нести с собой какой-нибудь компромисс. Поэтому всегда нужно точно знать, как правильно использовать инструмент и каково будет его влияние. Хочу отметить, что я выступаю не против инструментов, а против их неправильного использования, как сказано в твите (пост от Рафы Араужо, описывающее плохое использование MockK сообществом Android. Как тестировать на Android): «Каждый { call(any(),any(),any(),any(),any(),any()) } просто запускает  verify(exactly = 1) { call() } Печально, что в сообществе Android укоренилась культура использования моков» В зависимости от вашей ситуации вы также можете рассмотреть возможность использования классической школы тестирования, чтобы сократить шаблонный код дублеров в тесте. Как описано в твите: (пост Джейка Вартона, в котором идет речь о классической школе тестирования и рекомендуется использовать фейки): Фейки легко использовать повторно, и их не нужно настраивать для каждого отдельного теста. Просто они всегда ведут себя корректно. Или, что еще лучше, не используйте фейки, а используйте реальное поведение и тестируйте на более высоком уровне абстракции. В следующем разделе мы рассмотрим некоторые стратегии касательно тестовых дублеров, которые можно использовать в кодовой базе Android. Эти стратегии можно применять независимо от выбранной вами конфигурации дублеров (ручной или с применением инструментов). В основании тестовой пирамиды располагаются неинструментальные тесты, которые не используют эмуляторы или реальные устройства во время их выполнения. Хорошо известно, что тесты в этой группе обладают высокой степенью изолированности и быстродействия, поэтому дублеры здесь используются очень часто. Чтобы продемонстрировать использование тестовых дублеров в среде Android, давайте посмотрим, как будет выглядеть приложение в MVVM-архитектуре: Исходя из своего опыта, я могу сказать, что школа одиночного тестирования является наиболее распространенной в кодовых базах Android. И в большинстве случаев вы будете работать над проектом, который уже (вполне возможно, что неправильно) использует тестовые дублеры. Поэтому я попытаюсь продемонстрировать стратегию тестирования, которая использует школу одиночного тестирования, и то, как вы можете улучшить такой сетап: В приведенной выше стратегии мы используем следующие рассуждения: Тесты слоя View не очень распространены в неинструментальной части. Для их создания нам, вероятно, потребуется создать Robolectric, который, как известно, медленнее, чем чистые Java/Kotlin тесты. Если вы тестируете свои View, поскольку мы имеем дело с дорогостоящим тестом, я рекомендую не использовать дублеров для ViewModel, а использовать для репозитория. Причина в том, что ViewModels (по своей структуре) являются компонентами, тесно связанными с View, и поэтому поведение View было бы лучше представлено, если бы мы использовали реальные ViewModel. Кроме того, мы не получим большого выигрыша в скорости, если мы будем использовать дублеры для ViewModel, этот тест все равно был бы медленным. В этом конкретном примере можно было бы заменить стабом репозиторий. Чтобы протестировать слой ViewModel, мы вполне можем выполнить модульный тест вне Android и заменить репозиторий стабом. Другие зависимости ViewModel также могут быть заменены тестовыми дублерами. Чтобы протестировать уровень репозитория, мы также можем выполнить модульный тест вне Android. Здесь мы заменяем локальный и удаленный источники данных на стабы или фейки. Чтобы протестировать слой удаленного источника данных, нам, вероятно, придется заменить каким-нибудь дублером серверную часть. Самый простой способ — создать фейк с помощью MockWebServer, который будет имитировать HTTP-сервер, отвечающий за возврат предварительно настроенных ответов. Тесты на этом уровне полезны для подтверждения того, что сериализация ответов бэкенда работает правильно. Чтобы протестировать уровень локального источника данных, нам, вероятно, придется заменить фейком или стабом локальную базу данных. Если мы хотим провести такое тестирование на неинструментальном уровне, Robolectric также может подойти. Тем не менее, я считаю, что этот тип тестирования больше подходит для инструментальной части вашего набора тестов. Чтобы протестировать слой данных изолированно, лучше делать это более надежно. Имейте в виду, что это всего лишь пример: если у вас есть хорошо структурированная стратегия тестирования с дублерами и вы хорошо используете ее в своей кодовой базе, у вас будет меньше проблем с переходом на другую школу тестирования или заменой инструментов, если это вдруг окажется необходимо. Наконец, помните, что лучший подход всегда будет зависеть от контекста вашей команды разработчиков. Тестовые дублеры также очень важны для инструментальной части пирамиды, но здесь их следует использовать немного осторожнее. Загрузка ресурсов Android и запуск тестов с использованием эмулятора или реального устройства, безусловно, является дорогостоящей операцией, возможно, даже более дорогой, чем подавляющее большинство неинструментальных тестов. Поскольку у инфраструктуры такого типа более высокая стоимость создания, мы должны использовать ее по максимуму и интегрировать как можно больше реальных компонентов и, следовательно, использовать меньше тестов дублеров. Очень распространенная ошибка, которую я часто вижу, — это тесты пользовательского интерфейса, заменяющие ViewModel дублерами. На мой взгляд, чем дороже тест, тем ближе вы должны быть к реальной реализации, а значит, вы должны попробовать покрыть как можно больше кода. Использование дублеров для замены ViewModel в тестах пользовательского интерфейса просто результирует в сложном и медленном тесте с низким охватом кода. Также следует обратить внимание на то, что большинство инструментов (например, MockK или Mockito) манипулируют байт-кодом для создания моков и стабов. Поскольку Android использует собственную виртуальную машину и генерирует байт-код в определенном формате (Dalvik), некоторые из этих инструментов имеют свои ограничения на создание дублеров в этом инструментальном слое. По этим причинам я не советую использовать дублеры в инструментальных тестах. За исключением двух ситуаций: Когда нужно заменить зависимости от границ ввода-вывода (I/O boundaries), таких как бэкенды и базы данных, более быстрыми и более детерминированными дублерами. Например, используя такие инструменты, как MockWebServer. Или заменить сторонние инструменты, которые сложно конфигурировать в инструментальных тестах, например классы Firebase. Инструменты внедрения зависимостей, такие как Dagger, Hilt или Koin, и Product Flavors могут быть отличными союзниками при использовании тестовых дублеров на инструментальном уровне. Как мы видели в предыдущих разделах, у нас есть дублеры, которые имеют схожие цели, но реализованы и настроены по-разному. Дискуссии о том, как вы должны использовать дублеров, и о компромиссах, связанных с простотой конфигурации и обслуживанием, вызвали множество интересных дебатов и публикаций в сообществе разработчиков. Взаимосвязь между хрупкостью тестов и рефакторингом — одна из самых распространенных тем, которые вам предстоит увидеть. Вот всего несколько примеров: 1) Твит Райана Хартера, рекламирующий его (отличную) статью о замене моков на фейки. Возможно, термин «шпион» (вместо «фейк») был бы более уместен в его статье: После хорошего обсуждения с @HandstandSam и другими, я написал небольшой пост о своем подходе к замене моков на фейки. Отвечая @HandstandSam: Мне любопытно, почему вы вообще решили использовать mockito в этой ситуации. Это выглядит как довольно стандартный фейк, который можно реализовать с 3 свойствами, чтобы вообще не использовать мок-инстанс и просто проверять состояние. 2) Твит Зака ​​Свирса, явно неудовлетворенного тем, как Mockito создает тестовых дублеров, что, по его мнению, способствует хрупкости тестов: «Помогите, я попал в ад»: 3) Пост Джейка Уортона, выступающего против использования моков, вероятно, в соответствии с классическим подходом к тестированию: Отказ от моков сэкономило мне огромное количество времени при рефакторинге. Вместо обновления 100 скриптов при изменении деталей реализации я обновляю один тест изменения наблюдаемого поведения. Теперь вы должны быть в состоянии ответить или, по крайней мере, понять вопросы, заданные во введении к первой статье: «Нам просто нужно мокнуть эту зависимость, и все будет работать нормально» «Избегайте моков!» «Моки или стабы?» «Предпочитаю мокам фейки”  Тестовые дублеры (также зачастую называемые моками) очень важны в рамках ваших стратегий тестирования. Хорошо изучите их концепции, разберитесь в связанных с ними компромиссах и, таким образом, вы сможете сделать тестирование ваших Android-приложений масштабируемым и в то же время простым. XUnitPattens by Gerard Meszaros Test double definition by Martin Fowler Unit test definition by Martin Fowler Mocks Aren’t Stubs by Martin Fowler Mocks and Stubs aren’t Spies by Hamlet D’Arcy Unit Testing: Mocks, Stubs and Spies Gabo Esquivel Testing on Toilet by Google Replacing Mocks by Ryan Harter Prefer Fakes Over Mocks by Alexey Golub Уже завтра вечером в OTUS состоится открытое занятие «UI Profiling. Обзор возможностей тестирования производительности приложений и инструментов оптимизации». На нем разберёмся в том, что такое «тормозящее приложение», рассмотрим основные причины такого поведения, и инструменты, призванные найти и исправить эту проблему. Но мало понять, какова производительность приложения на вашем устройстве. Поэтому мы рассмотрим несколько сервисов, позволяющих измерить производительность в бою — на телефонах ваших пользователей. Присоединяйтесь, perf matters! Регистрация по ссылке.  Переводчик, редактор