Создание модульных тестов с помощью библиотеки тестирования Health Connect.

Библиотека тестирования Health Connect ( androidx.health.connect:connect-testing ) упрощает создание автоматизированных тестов. Вы можете использовать эту библиотеку для проверки поведения вашего приложения и подтверждения его корректной реакции на нестандартные ситуации, которые сложно протестировать вручную.

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

Чтобы начать использовать библиотеку, добавьте ее как тестовую зависимость:

 testImplementation("androidx.health.connect:connect-testing:1.0.0-alpha01")

Точкой входа в библиотеку является класс FakeHealthConnectClient , который используется в тестах для замены HealthConnectClient . FakeHealthConnectClient обладает следующими функциями:

  • Представление записей в памяти, позволяющее вставлять, удалять, удалять и читать их.
  • Генерация токенов изменений и отслеживание изменений
  • Пагинация для записей и изменений
  • Агрегационные ответы поддерживаются заглушками
  • Позволяет любой функции генерировать исключения
  • FakePermissionController , который можно использовать для эмуляции проверки разрешений.

Чтобы узнать больше о замене зависимостей в тестах, прочтите статью «Внедрение зависимостей в Android» . Чтобы узнать больше о поддельных тестах, прочтите статью «Использование тестовых двойников в Android» .

Например, если класс, взаимодействующий с клиентом, называется HealthConnectManager и он принимает HealthConnectClient в качестве зависимости, он будет выглядеть так:

class HealthConnectManager(
    private val healthConnectClient: HealthConnectClient,
    ...
) { }

В тестах вместо этого вы можете передать тестируемому классу подделку:

import androidx.health.connect.client.testing.ExperimentalTestingApi
import androidx.health.connect.client.testing.FakeHealthConnectClient
import kotlinx.coroutines.test.runTest

@OptIn(ExperimentalTestingApi::class)
class HealthConnectManagerTest {

    @Test
    fun readRecords_filterByActivity() = runTest {
        // Create a Fake with 2 running records.
        val fake = FakeHealthConnectClient()
        fake.insertRecords(listOf(fakeRunRecord1, fakeBikeRecord1))

        // Create a manager that depends on the fake.
        val manager = HealthConnectManager(fake)

        // Read running records only.
        val runningRecords = manager.fetchReport(activity = Running)

        // Verify that the records were filtered correctly.
        assertTrue(runningRecords.size == 1)
    }
}

Этот тест проверяет, что вымышленная функция fetchReport в HealthConnectManager правильно фильтрует записи по активности.

Проверка исключений

Практически каждый вызов HealthConnectClient может вызывать исключения. Например, в документации к insertRecords упоминаются следующие исключения:

  • @throws android.os.RemoteException для любых сбоев транспортировки IPC.
  • @throws SecurityException для запросов с неразрешенным доступом.
  • @throws java.io.IOException при любых проблемах с дисковым вводом-выводом.

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

import androidx.health.connect.client.testing.stubs.stub

@Test
fun addRecords_throwsRemoteException_errorIsExposed() {
    // Create Fake that throws a RemoteException
    // when insertRecords is called.
    val fake = FakeHealthConnectClient()
    fake.overrides.insertRecords = stub { throw RemoteException() }

    // Create a manager that depends on the fake.
    val manager = HealthConnectManager(fake)

    // Insert a record.
    manager.addRecords(fakeRunRecord1)

    // Verify that the manager is exposing an error.
    assertTrue(manager.errors.size == 1)
}

Агрегация

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

Например, вы можете запрограммировать агрегатную функцию на возврат определенного результата:

import androidx.health.connect.client.testing.AggregationResult
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.ExerciseSessionRecord
import java.time.Duration

@Test
fun aggregate() {
    // Create a fake result.
    val result =
        AggregationResult(metrics =
            buildMap {
                put(HeartRateRecord.BPM_AVG, 74.0)
                put(
                    ExerciseSessionRecord.EXERCISE_DURATION_TOTAL,
                    Duration.ofMinutes(30)
                )
            }
        )

    // Create a fake that always returns the fake
    // result when aggregate() is called.
    val fake = FakeHealthConnectClient()
    fake.overrides.aggregate = stub(result)

Затем вы можете проверить, что тестируемый класс (в данном случае HealthConnectManager ) правильно обработал результат:

// Create a manager that depends on the fake.
val manager = HealthConnectManager(fake)
// Call the function that in turn calls aggregate on the client.
val report = manager.getHeartRateReport()

// Verify that the manager is exposing an error.
assertThat(report.bpmAverage).isEqualTo(74.0)

Разрешения

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

Тестируемый объект может использовать PermissionController—through свойство permissionController интерфейса HealthConnectClient ) для проверки разрешений. Обычно это делается перед каждым вызовом клиента.

Чтобы протестировать эту функциональность, вы можете указать, какие разрешения доступны, с помощью FakePermissionController :

import androidx.health.connect.client.testing.FakePermissionController

@Test
fun newRecords_noPermissions_errorIsExposed() {
    // Create a permission controller with no permissions.
    val permissionController = FakePermissionController(grantAll = false)

    // Create a fake client with the permission controller.
    val fake = FakeHealthConnectClient(permissionController = permissionController)

    // Create a manager that depends on the fake.
    val manager = HealthConnectManager(fake)

    // Call addRecords so that the permission check is made.
    manager.addRecords(fakeRunRecord1)

    // Verify that the manager is exposing an error.
    assertThat(manager.errors).hasSize(1)
}

Пагинация

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

Тестируемый объект, HealthConnectManager в нашем примере, может указать размер страницы в ReadRecordsRequest :

fun fetchRecordsReport(pageSize: Int = 1000) }
    val pagedRequest =
        ReadRecordsRequest(
            timeRangeFilter = ...,
            recordType = ...,
            pageToken = page1.pageToken,
            pageSize = pageSize,
        )
    val page = client.readRecords(pagedRequest)
    ...

Установка небольшого значения размера страницы, например, 2, позволяет протестировать пагинацию. Например, вы можете вставить 5 записей, чтобы readRecords вернул 3 разные страницы:

@Test
fun readRecords_multiplePages() = runTest {

    // Create a Fake with 2 running records.
    val fake = FakeHealthConnectClient()
    fake.insertRecords(generateRunningRecords(5))

    // Create a manager that depends on the fake.
    val manager = HealthConnectManager(fake)

    // Read records with a page size of 2.
    val report = manager.generateReport(pageSize = 2)

    // Verify that all the pages were processed correctly.
    assertTrue(report.records.size == 5)
}

Тестовые данные

Библиотека пока не включает API для генерации поддельных данных, но вы можете использовать данные и генераторы, используемые библиотекой в ​​Android Code Search .

Для имитации значений метаданных в тестах можно использовать MetadataTestHelper . Он предоставляет функцию расширения populatedWithTestValues() , которая имитирует заполнение значений метаданных Health Connect во время добавления записи.

Заглушки

Свойство overrides класса FakeHealthConnectClient позволяет запрограммировать (или заглушить ) любую из его функций так, чтобы они генерировали исключения при вызове. Вызовы агрегации также могут возвращать произвольные данные, и поддерживается постановка нескольких ответов в очередь. Подробнее см. в разделах Stub и MutableStub .

Сводка пограничных случаев

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