Создание модульных тестов с помощью библиотеки тестирования 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 , который можно использовать для эмуляции проверок разрешений.

Подробнее о замене зависимостей в тестах читайте в статье Dependency Injection in 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 .

Незавершённые версии

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

Краткое изложение крайних случаев

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