Библиотека Health Connect Testing ( androidx.health.connect:connect-testing ) упрощает создание автоматизированных тестов. Вы можете использовать эту библиотеку для проверки поведения вашего приложения и подтверждения того, что оно корректно реагирует на нестандартные ситуации, которые сложно протестировать вручную.
Вы можете использовать эту библиотеку для создания локальных модульных тестов , которые обычно проверяют поведение классов в вашем приложении, взаимодействующих с клиентом Health Connect .
Для начала использования библиотеки добавьте её в качестве тестовой зависимости:
testImplementation("androidx.health.connect:connect-testing:1.0.0-alpha03")
Точкой входа в библиотеку является класс 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в случае любых сбоев передачи данных по межпроцессному взаимодействию. -
@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 доступ к которому осуществляется через свойство 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 .
Краткое описание крайних случаев
- Убедитесь, что ваше приложение работает должным образом, когда клиент генерирует исключения. Изучите документацию каждой функции, чтобы выяснить, какие исключения следует проверять.
- Убедитесь, что перед каждым звонком клиенту выполняется соответствующая проверка прав доступа.
- Проверьте правильность реализации пагинации.
- Проверьте, что происходит, когда вы загружаете несколько страниц, но у одной из них истек срок действия токена.