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)
}
}
此测试验证HealthConnectManager
中虚构的fetchReport
函数是否通过活动正确过滤记录。
验证异常
几乎对HealthConnectClient
的每次调用都可能抛出异常。例如,insertRecords
的文档中提到了这些异常
@throws android.os.RemoteException
用于任何 IPC 传输故障。@throws SecurityException
用于请求未经授权的访问。@throws java.io.IOException
用于任何磁盘 I/O 问题。
这些异常涵盖了诸如连接不良或设备空间不足等情况。您的应用必须正确响应这些运行时问题,因为它们可能随时发生。
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)
}
聚合
聚合调用没有模拟实现。相反,聚合调用使用您可以编程以特定方式运行的存根。您可以通过FakeHealthConnectClient
的overrides
属性访问这些存根。
例如,您可以编程聚合函数以返回特定结果。
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
。
您的被测对象可以通过HealthConnectClient
接口的permissionController
属性使用PermissionController
来检查权限。这通常在每次调用客户端之前进行。
要测试此功能,您可以使用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 代码搜索中使用的數據和生成器。
存根
FakeHealthConnectClient
的overrides
属性允许您编程(或*模拟*)其任何函数,以便它们在被调用时抛出异常。聚合调用还可以返回任意数据,并且它支持排队多个响应。有关更多信息,请参阅Stub
和MutableStub
。
边缘情况总结
- 验证您的应用在客户端抛出异常时的预期行为。检查每个函数的文档以确定您应该检查哪些异常。
- 验证您对客户端的每次调用之前都进行了正确的权限检查。
- 验证您的分页实现。
- 验证当您获取多个页面但其中一个页面令牌已过期时会发生什么。