Android
 Computer >> コンピューター >  >> システム >> Android

Proto DataStoreのテストをマスターする:Android開発者のための実践ガイド

以前の記事では、AndroidアプリでProto DataStoreを活用する方法について解説しました。その記事は、実際のアプリ開発でProto DataStoreを使用した経験に基づいて執筆したものです。

今回はその続編として、そこで得た知識をもとに、Proto DataStoreに対してテストを書くとどのような体験になるのかを試してみました。

オンラインで関連情報を探してみましたが、参考になる資料はほとんど見つかりませんでした。そこで、同じように情報を求めている方々のために、私が学んだ内容を共有することにしました。最悪の場合でも、将来の自分への備忘録になるでしょう。

調査の過程で1つの記事を見つけましたが、その記事は主にPreferences DataStoreのテストに焦点を当てたもので、Proto DataStoreについては扱っていませんでした。ただし、記事の中には次のような記述がありました。

「ただし、この資料はProto DataStoreのテスト環境構築にも利用できます。Preferencesと非常に似ているからです。」

しかし、実際に試してみると、依存関係の追加以外には共通点がほとんどなく、Proto DataStoreをテストするには独自のロジックを用意する必要があることが分かりました。

セットアップ

まず、アプリのbuild.gradleファイルに以下の依存関係を追加します。

dependencies {
 ///.....
 androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
 debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
}

$compose_versionは、プロジェクトレベルのbuild.gradleファイルで定義した変数です。

次に、androidTestディレクトリに移動して新しいファイルを作成します。通常、Proto DataStoreとやり取りするリポジトリクラスがあるはずなので、ファイル名は「YourRepositoryClassNameTest」のように命名すると良いでしょう。ここではMyRepositoryTestという名前を使用します。

Proto DataStoreのインスタンス化

Proto DataStore自体のテストに入る前に、まずインスタンスを生成する必要があります。この点についてネット上のドキュメントを探しても、情報はかなり乏しいのが現状です。

通常(テスト以外のシナリオ)では、グローバルなContextを使って次のようにProto DataStoreをインスタンス化します。

private val Context.myDataStore: DataStore<MyItem> by dataStore(
 fileName = DATA_STORE_FILE_NAME,
 serializer = MyItemSerializer
 )

しかし、この方法はテストクラス内では使えません。コードをコピペすることはできても、DataStoreオブジェクトにアクセスできないからです。以下のようにアプリケーションコンテキストを取得することはできます。

ApplicationProvider.getApplicationContext()

しかし、myDataStoreオブジェクトはこのコンテキスト経由では取得できません。

では、どうすればよいのでしょうか?

DataStoreFactory.createを使う

先ほど参照した記事では、PreferenceDataStoreFactory.createメソッドを使ってPreferences DataStoreを作成する例が紹介されていました。

fun create( 
 corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null, 
 migrations: List<DataMigration<Preferences>> = listOf(),
 scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()), 
 produceFile: () -> File): DataStore<Preferences>

しかし、私たちが使うのはPreferences DataStoreではないため、この方法は使えません。代わりに役立つのが、DataStoreFactory.createメソッドです。

fun <T : Any?> create( 
 serializer: Serializer<T>, 
 corruptionHandler: ReplaceFileCorruptionHandler<T>? = null, 
 migrations: List<DataMigration<T>> = listOf(), 
 scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()), produceFile: () -> File): DataStore<T>

このメソッドには複数の引数があります(デフォルト値を持つものもあります)が、すべてを渡す必要はありません。ここでは以下の2つを渡します。

  • シリアライザクラス
  • Proto DataStore用のファイルを生成するラムダ式
dataStore = DataStoreFactory.create( 
 produceFile = { 
 testContext.dataStoreFile(TEST_DATA_STORE_FILE_NAME) }, 
 serializer = MyItemSerializer 
 )

testContextは次のようにして取得します。

private val testContext: Context = ApplicationProvider.getApplicationContext()

これでProto DataStoreの作成に成功したので、実際にテストを書いていきましょう。リポジトリクラスがProto DataStoreのインスタンスを依存関係として受け取る構成になっている場合は、DataStore作成後にリポジトリクラスのインスタンスも生成しておく必要があります。

 private val repository = MyRepository(datastore)

テストケースの作成

初期状態の検証テスト

最初に、Proto DataStoreの初期状態を確認するテストを作成しましょう。Proto DataStoreはFlowを公開しているので、それを利用できます。

@OptIn(ExperimentalCoroutinesApi::class)
 @Test
 fun repository_testFetchInitialState() {
 runTest {
 testScope.launch {
 val dataStoreObject = repository.myFlow.first()
 // ここにProto DataStoreに対して検証したい内容を記述します。
 // 例:初期値がfalseのフラグ
 assert(dataStoreObject.myFlag == false) 
 }
 }
 }

☝️ お気づきの方もいるかもしれませんが、ここではOptInアノテーションを使用しています。これは、現時点で使用しているAPIが実験的(experimental)であり、使用時に明示的にマークする必要があるためです。

DataStoreのFlowにアクセスするため、testScopeでラップする必要があります。TestScopeは次のように作成します。

@OptIn(ExperimentalCoroutinesApi::class)
private val dispatcher = TestCoroutineDispatcher()
@OptIn(ExperimentalCoroutinesApi::class)
private val testScope = TestCoroutineScope(dispatcher)

実行して、最初のProto DataStoreテストの成功を楽しんでください。

……とはいえ、喜んでいられるのはほんの数秒でしょう。

もう少し実用的なテストに取り掛かりましょう。

アイテム追加のテスト

Proto DataStore内にオブジェクトのリストが格納されており、アイテムを追加した際の状態をテストしたい場面を想定します。

@OptIn(ExperimentalCoroutinesApi::class)
 @Test
 fun repository_testAdditionOfItem() {
 runTest {
 testScope.launch {
 //1
 val item: MyItem = MyItem.newBuilder().setItemId(UUID.randomUUID().toString())
 .setItemDescription(TEST_ITEM_DESCRIPTION).build()
 //2
 repository.updateItem(item)
 //3
 val items = repository.myFlow.first().itemsList
 assert(items.size == 1)
 //4
 assert(items[0].itemDescription.equals(TEST_ITEM_DESCRIPTION))
 }
 }
 }
  1. Protobufが公開しているAPIを使ってテスト用アイテムを作成します
  2. MyRepositoryクラスに公開されているメソッドを使って、このアイテムをProto DataStoreに追加します
  3. Proto DataStoreが公開するFlowからアイテムのリストを取得します
  4. Proto DataStore内のアイテムが、先ほど作成したアイテムと一致することを確認します

DataStoreのリーク問題

上記のテストを一括で実行しようとすると、ランタイムエラーに遭遇することになります。

「There are multiple DataStores active for the same file: /data/user/0/com.example.app/files/datastore/dataStore_filename.pb. You should either maintain your DataStore as a singleton or confirm that there is no two DataStore's active on the same file (by confirming that the scope is cancelled).」(同じファイルに対して複数のDataStoreがアクティブになっています。DataStoreをシングルトンとして維持するか、同じファイル上で2つのDataStoreがアクティブになっていないことを確認してください。)

これは困りました。テストクラス内でDataStoreインスタンスは1つしか作成していないのに、です。

一体何が起きているのでしょうか?

原因は、プロパティデリゲート(Context.datastore)を使わずにDataStoreを作成していることにあります。この場合、アクセスのたびにDataStoreオブジェクトがシングルトンであることが保証されません。

回避策:削除と再作成

この問題を回避するために有効なのが、各テストケースごとにDataStoreを削除して再作成するアプローチです。DataStoreを削除するには、次のようにします。

@After
fun cleanup() {
 File(testContext.filesDir, "datastore").deleteRecursively()
}

そして、各テストの前に再作成します。

@Before
 fun setup() {
 dataStore = DataStoreFactory.create(
 produceFile = {
 testContext.dataStoreFile(TEST_DATA_STORE_FILE_NAME)
 },
 serializer = MyItemSerializer
 )
 }

完全なサンプルコードについては、元記事のリンク先を参照してください。

まとめ

この記事では、Proto DataStoreをテストする際の基本的な流れを紹介しました。

ここでは2つのテストケースを取り上げましたが、あなたのDataStoreやそこに設定した型によっては、さらに多くのテストケースやシナリオが必要になるかもしれません。必要な構成要素はすでに揃っています。あとは自分のニーズに合わせて適応させるだけです。

  1. Poco X6 Proを最適化してサクサク動作を取り戻す!実践した5つの設定

    筆者は3年以上の経験を持つテックライターで、Androidとそのエコシステムを中心に、ガジェットのテスト・レビュー・執筆を手がけている。初めてのSamsung Galaxy端末との出会いをきっかけにAndroidの世界へ入り、今では消費者向けテクノロジーへの情熱となっている。化学の学士号を持ち、その分析的な視点を活かして、複雑な技術概念を分かりやすく実行しやすいアドバイスへと落とし込むのが得意だ。趣味はアニメの一気見と、レシピ本を読みながら次の食事の計画を立てること。 Poco X6 Proが重くなったきっかけ 私のPoco X6 Proは2024年初頭から愛用している一台だ。発売日の翌日に

  2. スマホだけでWebアプリを開発する方法 – Python × Pydroid 3 Androidアプリ開発チュートリアル

    こんにちは!私は18歳のバックエンド開発者で、機械学習エンジニアを目指して勉強中です。この記事では、スマートフォン1台とPythonだけを使ってWebアプリを構築する方法を解説します😁。それでは早速始めましょう!必要なものまず必要なのは、Android 6.0以降を搭載したスマートフォンだけです。「それだけ?」と思われるかもしれませんが、本当にこれだけでOKです。次に、スマホに「Pydroid 3」というモバイルアプリをインストールしましょう。ご覧のとおり、Pydroid 3はスマートフォン上でPythonコードを書けるようにしてくれるアプリです。Google Playストアからインストール