Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
DataStoreをテストする
Search
mkeeda
April 04, 2025
Programming
0
430
DataStoreをテストする
Shibuya.apk #52の発表資料です
https://shibuya-apk.connpass.com/event/349013/
mkeeda
April 04, 2025
Tweet
Share
More Decks by mkeeda
See All by mkeeda
時計仕掛けのCompose
mkeeda
1
400
What's new in Firebase for building gen AI features気になったところ
mkeeda
0
720
手動DIの教訓
mkeeda
0
210
WebViewと向き合う
mkeeda
2
1.4k
お気に入りのAndroid Studio小技集
mkeeda
0
350
Scalable UI testing solutions かんたんまとめ
mkeeda
0
1.1k
5分で分かるビルドロジック共通化の今
mkeeda
1
1.5k
Compose で手に入れた UI の Unit test
mkeeda
3
2k
Jetpack Compose 完全に理解した
mkeeda
1
3.8k
Other Decks in Programming
See All in Programming
Infer入門
riru
4
1.5k
リッチエディターを安全に開発・運用するために
unachang113
1
380
一人でAIプロダクトを作るための工夫 〜技術選定・開発プロセス編〜 / I want AI to work harder
rkaga
12
2.6k
No Install CMS戦略 〜 5年先を見据えたフロントエンド開発を考える / no_install_cms
rdlabo
0
480
The State of Fluid (2025)
s2b
0
140
「リーダーは意思決定する人」って本当?~ 学びを現場で活かす、リーダー4ヶ月目の試行錯誤 ~
marina1017
0
220
画像コンペでのベースラインモデルの育て方
tattaka
3
1.6k
抽象化という思考のツール - 理解と活用 - / Abstraction-as-a-Tool-for-Thinking
shin1x1
1
980
CLI ツールを Go ライブラリ として再実装する理由 / Why reimplement a CLI tool as a Go library
ktr_0731
3
1.1k
Flutter로 Gemini와 MCP를 활용한 Agentic App 만들기 - 박제창 2025 I/O Extended Seoul
itsmedreamwalker
0
130
パスタの技術
yusukebe
1
370
可変性を制する設計: 構造と振る舞いから考える概念モデリングとその実装
a_suenami
10
1.7k
Featured
See All Featured
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
126
53k
Put a Button on it: Removing Barriers to Going Fast.
kastner
60
4k
Building Applications with DynamoDB
mza
96
6.5k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
29
1.8k
Being A Developer After 40
akosma
90
590k
GraphQLの誤解/rethinking-graphql
sonatard
71
11k
What's in a price? How to price your products and services
michaelherold
246
12k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
161
15k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
358
30k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
248
1.3M
The Language of Interfaces
destraynor
158
25k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
1.3k
Transcript
DataStoreをテストする Shibuya.apk # 5 2 向井 田 一 平 (@mr_mkeeda)
• 向井 田 一 平 (むかいだ いっぺい) • 𝕏 :
@mr_mkeeda • Github: @mkeeda • Android Engineer at Cybozu, Inc • 最近ハマっているもの: ガンニバル🍖 2
DataStore “を” テストする • androidx.DataStore • DataStoreの処理 自 体をテストする •
ex. 独 自 実装のSerializerを使ったデータの読み書きをテスト • DataStoreを含めたテスト対象をテストする • ex. DataStoreをモックせずにRepositoryの実装をテスト 3
DataStoreテストの流れ 1 . DataStoreインスタンスを作成 • CoroutineDispatcherの差し替え ⭐ 2 . 前提の状況を作る
• 事前にデータを書き込んでおく • 別のテストケースで書き込んだデータの削除 ⭐ 3 . テストしたい処理を実 行 4 . 期待する振る舞いを検証 • データ書き込みしたファイルの中 身 を検証 ⭐ 4
CoroutineDispatcherの変え 方 5 val Context.testDataStore by dataStore( fileName = "test_file",
serializer = StringSerializer(defaultValue = "default"), scope = CoroutineScope(UnconfinedTestDispatcher() + Job()) // ⭐ ) // or val testDataStore = DataStoreFactory.create( serializer = StringSerializer(defaultValue = "default"), scope = CoroutineScope(UnconfinedTestDispatcher() + Job()), // ⭐ produceFile = { context.dataStoreFile(fileName = "test_file") } )
データの後始末 6 internal fun Context.clearDataStore() { File(filesDir, "/datastore").deleteRecursively() } @Before
fun setUp() { context.clearDataStore() } // or val testDataStore = DataStoreFactory.create( serializer = StringSerializer(defaultValue = "default"), produceFile = { /** Ұ࣌ϑΝΠϧΛ࡞Δ **/ } )
書き込まれたファイルの検証 方 法 7 val testDataStore = DataStoreFactory.create( serializer =
StringSerializer("default"), produceFile = { context.dataStoreFile(fileName = "test_file") } ) testDataStore.updateData { "Hello world" } // ⭐ val storedFile = context.filesDir.findFileBy("test_file") storedFile ?. readText() .shouldBe("Hello world") .shouldNotBe("default")
8 internal fun File.findFileBy(fileName: String): File? = searchFileRecursively(directory = this,
fileName) private fun searchFileRecursively( directory: File, fileName: String, ): File? { directory.listFiles() ?. forEach { file -> if (file.isDirectory) { searchFileRecursively(file, fileName) ?. let { return it } } else if (file.name.contains(fileName)) { return file } } return null }
ファイル参照の競合 • DataStoreインスタンスは シングルトンで使う設計 • 同 一 のファイル名を 同時に参照するとエラー •
AndroidTestだと1プロセス内で 同 一 参照があるとエラー • 複数のテストを実 行 しないと エラーに気付けない! 9 DataStore ファイル class TestA class TestB fun test 1 () fun test 2 () fun test 3 () ⚠ ⚠ ⚠
ファイル参照の競合を回避する • ファイル名をテストケース単位で ユニークにする • JUnitのRuleを作るといいかも (試してない) 1 0 DataStore
TestA_test 1 _ fi le class TestA class TestB fun test 1 () fun test 2 () fun test 3 () DataStore TestA_test 2 _ fi le DataStore TestB_test 3 _ fi le
ファイルの参照を解放させたい • DataStoreは稼働中、書き込み先のファイルを占有する • 2番 目 に参照してきたDataStoreはエラーになる • では、こういうのはどうやってテストする? 1
. DataStoreAでデータを書き込み 2 . アプリ再起動などでDataStoreAが破棄 3 . 1で書き込んだデータをDataStoreBで読み込める 1 1
1 2 val testDataStore1 = DataStoreFactory.create( produceFile = { context.dataStoreFile(fileName
= "test_file") } ) testDataStore1.updateData { "Hello world" } // ΞϓϦϓϩηεΛऴྃͤ͞Δ // ʁʁʁ⭐ val testDataStore2 = DataStoreFactory.create( produceFile = { context.dataStoreFile(fileName = "test_file") } ) // ॻ͖ࠐΜͩσʔλ͕ಡΈࠐΊΔ͜ͱΛ֬ೝ testDataStore2.data.first() shouldBe "Hello world"
Jobを完了させるとDataStoreはファイル参照を解放する 1 3 val dataStoreJob = Job() val testDataStore1 =
DataStoreFactory.create( scope = CoroutineScope( UnconfinedTestDispatcher() + dataStoreJob // ⭐ ), produceFile = { context.dataStoreFile(fileName = "test_file") } ) dataStoreJob.complete()
DataStoreテストまとめ • 基本編 • CoroutineDispatcherはscopeを差し替える • テスト後の後始末はファイルごと消す • DataStoreの書き込み挙動はファイルの中 身
を検証する • 上級編 • ファイル参照の競合に気をつける • DataStoreのファイル参照を解放するには scopeに含めたJobを完了させる 1 4
参考 • DataStore and testing | Android Developers | Medium
https://medium.com/androiddevelopers/datastore-and-testing- edf 7 ae 8 df 3 d 8 • androidx.DataStoreのテストコード https://cs.android.com/androidx/platform/frameworks/support/+/ androidx-main:datastore/ 1 5