헬스 커넥트 테스트 라이브러리를 사용하여 단위 테스트 만들기

헬스 커넥트 테스트 라이브러리 (androidx.health.connect:connect-testing)를 사용하면 자동 테스트를 간편하게 만들 수 있습니다. 이 라이브러리를 사용하여 애플리케이션의 동작을 확인하고 수동으로 테스트하기 어려운 드문 사례에 올바르게 응답하는지 검증할 수 있습니다.

이 라이브러리를 사용하여 헬스 커넥트 클라이언트와 상호작용하는 앱의 클래스 동작을 일반적으로 확인하는 로컬 단위 테스트를 만들 수 있습니다.

라이브러리 사용을 시작하려면 테스트 종속 항목으로 추가하세요.

 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 문서에는 다음과 같은 예외가 언급되어 있습니다.

  • IPC 전송 실패의 경우 @throws android.os.RemoteException
  • @throws SecurityException: 허용되지 않은 액세스 권한이 있는 요청의 경우
  • 디스크 I/O 문제의 경우 @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)
}

집계

집계 호출에는 가짜 구현이 없습니다. 대신 집계 호출은 특정 방식으로 작동하도록 프로그래밍할 수 있는 스텁을 사용합니다. FakeHealthConnectClientoverrides 속성을 통해 스텁에 액세스할 수 있습니다.

예를 들어 집계 함수가 특정 결과를 반환하도록 프로그래밍할 수 있습니다.

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—throughpermissionController 속성을 사용하여 권한을 확인할 수 있습니다. 이는 일반적으로 클라이언트에 대한 모든 호출 전에 실행됩니다.

이 기능을 테스트하려면 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와 같은 작은 값으로 설정하면 페이지로 나누기를 테스트할 수 있습니다. 예를 들어 readRecords가 서로 다른 3개의 페이지를 반환하도록 5개의 레코드를 삽입할 수 있습니다.

@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 코드 검색에서 라이브러리에서 사용하는 데이터와 생성기를 사용할 수 있습니다.

테스트에서 메타데이터 값을 모의 처리하려면 MetadataTestHelper를 사용하면 됩니다. 이렇게 하면 레코드 삽입 중에 메타데이터 값을 채우는 Health Connect를 시뮬레이션하는 populatedWithTestValues() 확장 프로그램 함수가 제공됩니다.

스텁

FakeHealthConnectClientoverrides 속성을 사용하면 호출 시 예외가 발생하도록 함수를 프로그래밍 (또는 스텁 처리)할 수 있습니다. 집계 호출은 임의의 데이터를 반환할 수도 있으며 여러 응답의 대기열 추가를 지원합니다. 자세한 내용은 StubMutableStub을 참고하세요.

예외 사례 요약

  • 클라이언트에서 예외를 발생시킬 때 앱이 예상대로 작동하는지 확인합니다. 각 함수의 문서를 확인하여 확인해야 하는 예외를 파악하세요.
  • 클라이언트에 대한 모든 호출이 적절한 권한 확인으로 시작되는지 확인합니다.
  • 페이지로 나누기 구현을 확인합니다.
  • 여러 페이지를 가져오지만 하나에 만료된 토큰이 있는 경우 발생하는 상황을 확인합니다.