ヘルスコネクト テスト ライブラリを使用して単体テストを作成する

ヘルスコネクト テスト ライブラリ(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
  • ディスク 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—through 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 などの小さい値に設定すると、ページネーションをテストできます。たとえば、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 を使用します。これにより、populatedWithTestValues() 拡張関数が提供されます。この関数は、レコード挿入時に Health Connect がメタデータ値を入力する動作をシミュレートします。

スタブ

FakeHealthConnectClientoverrides プロパティを使用すると、その関数を呼び出したときに例外がスローされるように、その関数をプログラム(またはスタブアウト)できます。集計呼び出しは任意のデータを返すこともでき、複数のレスポンスのキュー登録をサポートしています。詳細については、StubMutableStub をご覧ください。

エッジケースの概要

  • クライアントが例外をスローしたときにアプリが想定どおりに動作することを確認します。チェックする例外を特定するには、各関数のドキュメントを確認してください。
  • クライアントに対するすべての呼び出しの前に、適切な権限チェックが行われていることを確認します。
  • ページネーションの実装を確認します。
  • 複数のページを取得したときに、1 つのページのトークンが期限切れになった場合の動作を確認します。