TL; DR: Чтобы написать модульный тест JVM для версии сборки Android, вы можете: изменить поле версии сборки во время выполнения или создать оболочку для проверки версии сборки.

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

Build.VERSION

Чтобы получить информацию о версии, на которой работает ваше устройство, вам необходимо получить доступ к классу Build.VERSION:

SDK_INT: версия SDK программного обеспечения, которое в настоящее время работает на этом устройстве.

Посмотрите на код, вы увидите, что он получает значение из системных свойств:

int SDK_INT = SystemProperties.getInt("ro.build.version.sdk", 0);

Проверка версии сборки

Например, разрешения на выполнение требуются только для устройств, работающих на Android 6 (Marshmallow) и выше, поэтому вам нужно будет проверить следующее:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    // request Runtime Permissions
} else {
    // do work directly
}

Код выглядит простым, но когда речь идет о модульном тестировании, это не так.

Версия сборки модульного тестирования

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

К счастью, у нас есть 2 решения для этого:

  • Измените поле версии сборки во время выполнения.
  • Создайте оболочку для проверки версии сборки (рекомендуется).

Измените поле версии сборки во время выполнения

PowerMock

PowerMock - это фреймворк, позволяющий проводить модульное тестирование непроверяемого кода. Он предоставляет Whitebox.setInternalState() метод для установки значения поля:

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

Robolectric - это среда модульного тестирования Android, которая запускает ваши тесты в смоделированной среде Android внутри вашей локальной виртуальной машины Java. Он предоставляет ReflectionHelpers, который содержит методы для рефлексивного доступа к полям.

ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 23)

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

Отражение Java

Обе структуры тестирования, описанные выше, используют Java Reflection под капотом.

Отражение - это функция Java. Это позволяет исполняемой программе управлять внутренними свойствами и поведением класса во время выполнения.

Например, мы можем изменить конечную статическую константу, используя:

Чтобы повысить скорость тестирования, мы можем использовать Reflection для изменения поля SDK_INT в чистом Java-коде без накладных расходов на сторонних платформ тестирования.

Обязательно сбросьте это значение в @After методе, чтобы держать тест изолированным.

Обертка для проверки версии сборки (рекомендуется)

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

Интерфейс

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

Сначала создайте интерфейс, содержащий методы проверки версии сборки:

interface BuildVersionProvider {
    fun isMarshmallowAndAbove(): Boolean
}

Затем предоставьте для него реализацию:

class BuildVersionProviderImpl : BuildVersionProvider {
    override fun isMarshmallowAndAbove(): Boolean {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
    }
}

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

Затем в тестах при создании объекта SUT (Тестируемая система) для MyPresenter мы будем использовать фиктивный объект, реализующий этот интерфейс.

Но что такое фиктивный объект?

Макетные объекты

Имитация объекта - это один из видов тестовых двойников. Это смоделированный объект, контролируемым образом имитирующий поведение реального объекта.

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

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

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

Mockito

Mockito - самый популярный фреймворк mock для модульных тестов на Java.

Напишем модульные тесты

В тестовом классе создайте переменную для фиктивного объекта BuildVersionProvider (с пометкой @Mock) и переменную для реального объекта MyPresenter, который необходимо протестировать. В методе настройки (помеченном @Before) мы инициализируем фиктивные объекты, вызывая MockitoAnnotations.initMocks(), и используем этот фиктивный объект для создания реального MyPresenter объекта.

После этого мы готовы писать тесты.

Используйте Mockito.when(), чтобы предоставить ожидаемое значение, которое возвращается при вызове метода isMarshmallowAndAbove() для нашего фиктивного объекта. Вы также можете использовать объект макета представления, чтобы проверить предполагаемое поведение представления.

Наконец, наш код можно протестировать в быстрых модульных тестах JVM без использования сторонних платформ тестирования или Java Reflection!

Почему бы не использовать статические служебные методы?

На это есть несколько причин:

  • Вы не можете имитировать статический метод, используя только Mockito, вам нужно использовать другие среды тестирования, такие как PowerMock, которые изменяют файлы DEX.
  • Статические методы непросто протестировать или даже считаются непроверяемыми.

Заключение

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

Чтобы написать модульные тесты JVM для версии сборки Android, вы можете создать оболочку для проверки версии сборки. Для этого подхода может потребоваться некоторый шаблонный код, но он следует принципам SOLID, дает вам тестируемый код, не вмешиваясь в Java Reflection или сторонние платформы тестирования, и, что более важно, это быстро!

Удачного тестирования ~