Android robolectric apple silicone

Погружение в Robolectric

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

Под катом расскажу о внутреннем устройстве фреймворка для unit-тестирования Android-приложений — Robolectric.

Зачем тестировать Android-специфичный код?

Для начала постараемся ответить на вопрос — зачем тестировать код в местах интеграции с Android фреймворком?

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

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

SQLite — тестирование миграции данных, изменения схем, добавление новых таблиц, корректность выполнения запросов.

Intent / Bundle — для некоторых сценариев важно проверять корректность заполнения Intent, флаги, с которыми будет запущена следующая Activity или Service.

Не UI компоненты системы, такие как Camera, MediaPlayer, MediaRecorder, различные менеджеры и т.д.

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

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

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

RuntimeException c причиной — method not mocked при попытке запустить тест кода вызывающего какой — либо метод фреймворка. А если использовать следующую опцию в Gradle —

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

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

Пути решения

Для всех вышеперечисленных проблем существуют определенные решения:

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

Instrumented unit tests — самый точный вариант тестирования. Тесты выполняются на реальном устройстве или эмуляторе в настоящем окружении. Но за это придется расплачиваться долгой компиляцией, упаковкой APK, и медленным выполнением тестов.

Читайте также:  Android выключение экрана по таймеру

PowerMock + Mockito — PowerMock позволит вам мокать static методы и final классы. В этом случае вам придется частично повторить поведение некоторых классов Android, что может привести к распуханию кода ответственного за подготовку моков в ваших тестах и затруднит их поддержку в дальнейшем.

Robolectric

Существует еще одно решение проблемы Unit-тестирования Android приложений — Robolectriс. Robolectric — это фреймворк, разработанный компанией PivotalLabs в 2010 году. Он занимает промежуточное положение между “голыми” JUnit тестами и инструментированными тестами, запускаемыми на устройстве, симулируя реальное Android окружение. Фреймворк представляет собой скомпилированный android.jar с обвязкой из утилит для запуска тестов и упрощения тестирования. Он поддерживает загрузку ресурсов, примитивную реализацию выдувания View, предоставляет локальную SQLite (sqlite4java), легко кастомизируем и расширяем.

Используем android.util.Log

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

Реализуем следующий интерфейс — Logger , с одним методом для вывода сообщений уровня “Info”.

Напишем реализацию AndroidLogger — которая будет использовать android.util.Log .

Тестируем android.util.Log

Напишем тест на Junit с помощью Robolectric и убедимся, что метод info нашей реализации AndroidLogger на самом деле печатает сообщения в Logcat с уровнем info.

Аннотацией @RunWith мы указываем, что будем запускать тест с помощью RobolectricTestRunner . В параметрах к аннотации @Config мы передаем класс BuildConfig и указываем версию Android SDK которую будет симулировать Robolectric.

В тесте мы вызываем метод info у объекта AndroidLogger . С помощью класса ShadowLog достаем последнее сообщение записанное в лог и делаем assert по его содержимому.

Внутреннее устройство

Внутреннее устройство Robolectric можно условно разделить на 3 части: Shadow классы, RobolectricTestRunner и InstrumentingClassLoader .

Shadow классы

Создатели Robolectric вводят новый тип “тестовых двойников” (test double) — Shadow. Согласно официальному сайту, Shadows — “… not quite Proxies, not quite Fakes, not quite Mocks or Stubs”.

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

Связь Shadow c Robolectric

Аннотацией @Implements указывается класс для которого предназначен конкретный Shadow-класс.

В аннотации @Config теста можно указать Shadow-классы которые не входят в стандартную поставку Robolectric.

Переопределение методов

Переопределенный в Shadow-классе метод помечается аннотацией Implementation, важно сохранить сигнатуру оригинального метода.

При переопределении native метода кодовое слово native опускается.

Переопределение конструкторов

Для переопределения конструктора в Shadow-классе реализуется метод __constructor__ с теми же аргументами.

Вызов настоящего объекта

Для получения ссылки на реальный объект в Shadow-классе достаточно объявить поле с типом “оттеняемого” объекта помеченное аннотацией @RealObject :

Robolectric предоставляет возможность вызвать настоящую реализацию метода, минуя Shadow реализацию, с помощью Shadow.directlyOn .

Собственный Shadow

Написание собственного Shadow-класса не является большой проблемой, даже для сторонней библиотеки не входящий в стандартную поставку с Android.

Читайте также:  Gps навигаторы планшет android

Напишем класс, получающий токен пользователя с помощью GoogleAuthUtil .

Реализуем Shadow-класс для GoogleAuthUtil позволяющий переопределить token для определенного Account :

Напишем тест для GoogleAuthInteractor с помощью Robolectric. В конфигурации к тесту укажем, что хотим использовать ShadowGoogleAuthUtil и инструментировать классы из пакета com.google.android.gms.auth .

RobolectricTestRunner

От Shadow классов перейдем к RobolectricTestRunner — это первая часть Robolectric с которой связываются ваши тесты. Раннер отвечает за динамическую загрузку зависимостей (Shadow-классы и android.jar для указанной версии SDK) во время выполнения тестов.

Robolectric конфигурируется аннотацией @Config , c помощью которой можно изменять параметры симулируемого окружения для тестового класса и для каждого теста в отдельности. Конфигурация для запуска тестов будет собираться последовательно по всей иерархии тестового класса от родителя к наследнику и, наконец, к самому тестируемому методу. Конфигурация позволяет настроить:

  • версию Android
  • путь к манифесту и ресурсам
  • список текущих квалификаторов
  • сторонние Shadow
  • дополнительные имена пакетов для инструментирования

InstrumentingClassLoader

Перед запуском тестов RobolectricTestRunner подменяет системный ClassLoader на InstrumentingClassLoader .

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

Robolectric не инструментирует классы из пакета java.* , поэтому вызовы методов отсутствующие в обыкновенной JVM, но добавленные в Android SDK, проксируются напрямую в Shadow в месте вызова.

В фреймворке существуют два варианта инструментирования загружаемых классов. Оригинальная реализация генерирует байткод, использующий внутренний интерфейс ClassHandler и реализующий его класс ShadowWrangler , по сути оборачивающая каждый вызов метода через Shadow-класс в отдельный Runnable подобный объект и вызывает его. В апреле 2015 года в проект был добавлен второй вариант модификации байткода, использующий JVM инструкцию invokeDynamic .

Во время инструментирования Robolectric добавляет к каждому загружаемому классу интерфейс ShadowedObject с одним единственным методом — $$robo$getData() , в котором настоящий объект возвращает свой Shadow.

Для каждого конструктора InstrumentingClassLoader создает приватный метод $$robo$$__constructor__ с сохранением его сигнатуры и инструкций (кроме вызова super ).

В свою очередь тело оригинального конструктора будет состоять из:

  • Вызова super (если класс является наследником)
  • Вызова приватного метода $$robo$init , который инициализирует приватное поле __robo_data__ соответствующим Shadow объектом
  • Вызова переопределенного конструктора ( __constructor__ ) на Shadow объекте, если Shadow объект существует и соответствующий конструктор переопределен, в противном случае будет вызвана настоящая реализация ( $$robo$$__constructor__ ).

Конструктор модифицированный с использованием инструкции invokeDynamic :

Конструктор модифицированный с использованием ClassHandler:

Для инструментирования методов Robolectric использует аналогичный механизм, настоящий код метода выделяется в приватный метод с приставкой $$robo$$ и вызов метода делегируется Shadow объекту.

Метод модифицированный с использованием инструкции invokeDynamic :

Для native методов Robolectric опускает соответствующий модификатор и возвращает значение по умолчанию если этот метод не переопределен в Shadow классе.

Производительность

Robolectric далеко не самый производительный фреймворк. Запуск пустого теста на RobolectricTestRunner занимает около 2х секунд. По сравнению с “чистыми” JUnit тестами 2 секунды это существенная задержка.

Читайте также:  Активация навител для андроид код активации

Профилирование выполнения тестов на Robolectric показывает, что большую часть времени фреймворк тратит на инструментирование загружаемых классов.
Ниже приведены результаты профилирования Robolectric и связки PowerMock + Mockito для теста android.util.Log описанного выше.

Метод мс.
java.lang.ClassLoader.loadClass(String) 913
org.robolectric.internal.bytecode.InstrumentingClassLoader.
getInstrumentedBytes(ClassNode, boolean)
767
org.objectweb.asm.tree.ClassNode.accept(ClassVisitor) 407
org.objectweb.asm.tree.MethodNode.accept(ClassVisitor) 367
org.robolectric.internal.bytecode.InstrumentingClassLoader
$ClassInstrumentor.instrument()
298
org.objectweb.asm.ClassReader.accept(ClassVisitor, Attribute[], int) 277
org.robolectric.shadows.ShadowResources.getSystem() 268
Метод мс.
org.powermock.api.extension.proxyframework.ProxyFrameworkImpl.isProxy(Class) 304
org.powermock.api.mockito.repackaged.cglib.core.KeyFactory$Generator
.generateClass(ClassVisitor)
131
sun.launcher.LauncherHelper.checkAndLoadMain(boolean, int, String) 103
javassist.bytecode.MethodInfo.rebuildStackMap(ClassPool) 85
java.lang.Class.getResource(String) 84
org.mockito.internal.MockitoCore. () 67

Опыт использования

В настоящий момент в нашем проекте более 3000 Unit тестов, примерно половина из них используют Robolectric.

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

  • Parcelable
  • Форматирование строк в ресурсах
  • Не UI компоненты (Camera)

Для всех остальных случаев мы оборачиваем зависимости Android в легко тестируемые обертки или используем unmock-plugin для Gradle.

Видео с моим докладом на эту же тему на конференции MBLTdev 16

Источник

Android robolectric apple silicone

Android Unit Testing Framework

Latest commit

Git stats

Files

Failed to load latest commit information.

README.md

Robolectric is the industry-standard unit testing framework for Android. With Robolectric, your tests run in a simulated Android environment inside a JVM, without the overhead and flakiness of an emulator. Robolectric tests routinely run 10x faster than those on cold-started emulators.

Robolectic supports running unit tests for 15 different versions of Android, ranging from Jelly Bean (API level 16) to S (API level 31).

Here’s an example of a simple test written using Robolectric:

For more information about how to install and use Robolectric on your project, extend its functionality, and join the community of contributors, please visit http://robolectric.org.

Starting a New Project

If you’d like to start a new project with Robolectric tests you can refer to deckard (for either maven or gradle) as a guide to setting up both Android and Robolectric on your machine.

Building And Contributing

Robolectric is built using Gradle. Both IntelliJ and Android Studio can import the top-level build.gradle file and will automatically generate their project files from it.

Robolectric supports running tests against multiple Android API levels. The work it must do to support each API level is slightly different, so its shadows are built separately for each. To build shadows for every API version, run:

If you would like to live on the bleeding edge, you can try running against a snapshot build. Keep in mind that snapshots represent the most recent changes on master and may contain bugs.

Источник

Оцените статью