使用 Health Connect Testing 程式庫建立單元測試

健康資料同步測試程式庫 (androidx.health.connect:connect-testing) 可簡化自動化測試的建立作業。您可以使用這個程式庫驗證應用程式的行為,並確認應用程式是否能正確處理難以手動測試的罕見情況。

您可以使用這個程式庫建立本機單元測試,通常會驗證應用程式中與健康資料同步用戶端互動的類別行為。

如要開始使用程式庫,請將其新增為測試依附元件:

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

程式庫的進入點是 FakeHealthConnectClient 類別,您可以在測試中使用這個類別取代 HealthConnectClientFakeHealthConnectClient 具有下列功能:

  • 記錄的記憶體內表示法,方便您插入、移除、刪除及讀取記錄
  • 產生變更權杖及追蹤變更
  • 記錄和變更的分頁
  • 支援使用存根的匯總回應
  • 允許任何函式擲回例外狀況
  • 可用於模擬權限檢查的 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)
}

匯總

匯總呼叫沒有虛假實作。而是使用可程式化的虛設常式,以特定方式運作。您可以透過 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

受測主體可以使用 PermissionController—through HealthConnectClient 介面的 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 程式碼搜尋中使用程式庫使用的資料和產生器。

如要在測試中模擬中繼資料值,可以使用 MetadataTestHelper。這會提供 populatedWithTestValues() 擴充功能函式,模擬「健康資料同步」在插入記錄時填入中繼資料值。

存根

FakeHealthConnectClientoverrides 屬性可讓您程式化 (或存根化) 任何函式,以便在呼叫時擲回例外狀況。匯總呼叫也可以傳回任意資料,並支援將多個回應加入佇列。詳情請參閱 StubMutableStub

特殊案例摘要

  • 確認用戶端擲回例外狀況時,應用程式是否如預期運作。 請查看各函式的文件,瞭解應檢查哪些例外狀況。
  • 確認您對用戶端進行的每次呼叫,都先經過適當的權限檢查。
  • 確認分頁導入狀態。
  • 驗證擷取多個網頁時,如果其中一個網頁的權杖過期,會發生什麼情況。