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
Android Architecture Componentとテスト2019年7月版 / Te...
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
tkmnzm
July 25, 2019
Programming
1.2k
8
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Android Architecture Componentとテスト2019年7月版 / Testing Android Architecture Components in 2019-07
tkmnzm
July 25, 2019
More Decks by tkmnzm
See All by tkmnzm
AndroidアプリのUIバリエーションをあの手この手で確認する / Check UI variations of Android apps by various means
tkmnzm
1
1.6k
Androidアプリの良いユニットテストを考える / Thinking about good unit tests for Android apps
tkmnzm
5
10k
Google I:O 2023 Androidの自動テストアップデートまとめ / Google I:O 2023 Android Testing Update Recap
tkmnzm
0
690
コルーチンのエラーをテストするためのTips / Tips for testing Kotlin Coroutine errors
tkmnzm
0
1.3k
Androidのモダンな技術選択にあわせて自動テストも アップデートしよう / Update your automated tests to match Android's modern technology choices
tkmnzm
3
2.5k
SWET dev-vitalチームによるプロジェクトの健康状態可視化の取り組み / SWET dev-vital team's efforts to visualize the health of the project
tkmnzm
1
1.4k
モバイルアプリテスト入門 / Getting Started with Mobile App Testing
tkmnzm
1
630
25分で作るAndroid Lint / Android Lint made in 25 minutes
tkmnzm
0
1k
2年半ぶりのプロダクト開発であらためて感じた自動テストの大切さ / realized the importance of automatic testing with product development for the first time in two and a half years
tkmnzm
1
870
Other Decks in Programming
See All in Programming
Mujeres en SEO Summit 2026 - Greatest Disaster Hits en Web Performance
guaca
0
220
Agentic UI
manfredsteyer
PRO
0
210
作って学ぶ、 JSX (TSX) ランタイムの基本
syumai
7
1.7k
鹿野さんに聞く!『TypeScriptコードレシピ集』で磨く実践力
tonkotsuboy_com
4
910
ランチタイムLT会3周年!ランチタイムLT会を3年間続けられたお話
y0hgi
1
120
Spec Driven Development | AI Summit Lisbon
danielsogl
PRO
0
220
技術記事、 専門家としてのプログラマ、 言語化
mizchi
13
6.6k
Oxlintのカスタムルールの現況
syumai
6
1.2k
ローカルLLMでどこまでコードが書けるか -縮小版 / How much code can be written on a local LLM Shortened
kishida
2
120
Vue × Nuxt × Oxc どこまで使える?実運用の現在地
andpad
0
320
エンジニア向け会社紹介/Findy Company Profile
findyinc
6
360k
Language Server 使ってる? 〜VSCode と Zed の場合〜 / Are you using a Language Server? ~For VS Code and Zed~
handlename
0
810
Featured
See All Featured
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
3.6k
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.3k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
210
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.2k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1k
How STYLIGHT went responsive
nonsquared
100
6.2k
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
67
55k
AI: The stuff that nobody shows you
jnunemaker
PRO
8
750
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
570
Efficient Content Optimization with Google Search Console & Apps Script
katarinadahlin
PRO
1
640
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
2
230
Transcript
Architecture Componentとテストのまとめ [2019年7月版] DeNA SWETグループ Nozomi Takuma
自己紹介 Nozomi Takum DeNA SWETグループ Androidとテストが好き
Architecture Componentとテスト 日々進化するArchitecture Component テスト周りも同じくアップデートされる 現時点(2019年7月)での各componentのテストに関するトピックを 整理
本日登場するComponent ・ LiveData ・ ViewModel ・ Room ・ WorkManager ・
Navigation
LiveData Observe可能なデータホルダー Androidのライフサイクル(Activity/Fragment/Service)を意識して動 作 ViewModelやRoomなどその他のArchitecture componentと連携し て使われることが多い
LiveDataのテスト ・ ObserveForever ・ InstantTaskExecutorRule
ObserveForever LiveDataをobserveするときのメソッドは2種類 observe(lifecycleOwner, observer) observeFroever(observer) テストのときはLifecycleOwnerのインスタンスが不要な observeFroeverを使うのが楽
ObserveForever // Observer にどんな値が渡ってくるかを記録するためのspy val spyObserver = spyk<Observer<String>>() spy をobserveForever
viewmodel.strLiveData.observeForever(spyObserver) // strLiveData をTest にする実装 viewmodel.changeText("Test") verify { // onChanged の引数をVerify で検証 spyObserver.onChanged("test") }
InstantTaskExecutorRule 先程のテストコードをJunitで実行するとエラーになる java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. InstantTaskExecutorRuleを使用する
InstantTaskExecutorRule background executorを差し替えて同期的に実行してくれる android.arch.core:core‑testing class LiveDataTest { @get:Rule var instantExecutorRule
= InstantTaskExecutorRule() }
InstantTaskExecutorRule とてもわかりやすい解説 https://medium.com/@star_zero/livedataのunittest‑ 2b295d2818c1
ViewModel UIにひもづくデータの保持と管理を行う 画面のライフサイクルを意識して動作 ドメイン層・モデル層へのエンドポイントとして使用されることが 多い
ViewModelのテスト ・ ViewModelScope ・ SavedStateHandler
典型的なViewModelのテスト class TaskViewModelTest { @get:Rule var instantExecutorRule = InstantTaskExecutorRule() @Test
fun test() { val taskRepositoty = mock/fake TaskRepository val viewModel = TaskViewModel(taskRepositoty) val spyObserver = spyk<Observer<String>>() viewmodel.notifyText.observeForever(spyObserver) viewmodel.addTask("Test") verify { spyObserver.onChanged("add task:Test !") } } }
ViewModelScope ViewModelがDestroyするときに自動でcoroutineのキャンセルをし てくれる ViewModelのextension propertyとして定義 デフォルトはDispatchers.MainがCoroutine Dispatherとして使われ ている
ViewModelScope class TaskViewModel() : ViewModel() { fun addTask(taskName: String) {
viewModelScope.launch { .. } } }
Testing with ViewModelScope Dispathers.Mainをテスト用のCoroutineDispatherに差し替える org.jetbrains.kotlinx:kotlinx‑coroutines‑test val testDispatcher = TestCoroutineDispatcher() @Before
fun setup() { Dispatchers.setMain(testDispatcher) } @After fun teardown() { Dispatchers.resetMain() }
Testing with ViewModelScope Ruleとして定義しておくと便利 ref: https://bit.ly/2K10suX
Testing with ViewModelScope ViewModelScope内でdelayがあるときは、delayした分の時間をすすめ て処理を再開してあげる val testDispatcher = TestCoroutineDispatcher() ..
@Test fun testDelay() { val viewModel = TaskViewModel() // ViewModelScope 内でdelay(1_000) viewModel.delayMathod() // TestCoroutineDispatcher#advanceTimeBy // advanceUntilIdle() というのもある testDispatcher.advanceTimeBy(1_000) // assertion }
SavedStateHandler ViewModelからsavedInstanceStateへのアクセスが可能になる androidx.lifecycle:lifecycle‑viewmodel‑savedstate
SavedStateHandler class MyViewModel(val savedState: SavedStateHandle) : ViewModel() { val text
= savedStateHandle.getLiveData<String>("text") fun updateText() { savedStateHandle.set("text", "test") } }
SavedStateHandler class SavedStateViewModelTest { @get:Rule val testRule = InstantTaskExecutorRule() @Test
fun test() { // 空のSavedStateHandle を渡す val target = SavedStateViewModel(SavedStateHandle()) target.updateText() val spyObserver = spyk<Observer<String>>() target.text.observeForever(spyObserver) verify(spyObserver).onChanged("test") } }
Room SQLiteへのアクセスを抽象化 アノテーションから実装を生成 LiveDataでのテーブル監視、Rxやcoroutineのサポート、ビュー機 能など高機能
Roomのテスト ・ Dao Interface ・ in‑memory database ・ Robolectric support
Dao Interface RoomではDaoをinterfaceで定義する それによりDaoを使用するクラスはRoomの実装をテストダブルで 置き換えるのが容易 モックで置き換えたり、後述のin‑memory databaseと組み合わせ たりが可能
Dao Interface class TasksRepository (val tasksDao: TasksDao) val db =
Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "Tasks.db").build() val repo = TasksRepository(db.tasksDao()) モックへの置き換え val mock = mockk<Taskdao>() val repo = TasksRepository(mock)
in‑memory database メモリ上にデータをstoreしてくれる プロセスが消えたらデータも消える Fakeの用途でテストに使うことが可能
in‑memory database @RunWith(AndroidJUnit4::class) class TaskDaoTest { private lateinit var db:
TestDatabase @Before fun setUp() { val context = ApplicationProvider .getApplicationContext<Context>() db = Room.inMemoryDatabaseBuilder( context, TestDatabase::class.java).build() } @Test fun test() { // Room のin‑memory database のDAO 実装を注入 val repo = TasksRepository(db.tasksDao()) } @After fun tearDown() { db.close() }
Robolectric support RoomのテストはLocal Testでも動作する ただLocalでのテストは推奨ではない https://developer.android.com/training/data‑ storage/room/testing‑db#host‑machine InstrumentationTestでもLocal動作するようにテストファイルを配 置してあげるのがよさそう
WorkManager バッググラウンドタスクの実行・管理を行う 延期可能&必ず実行したいバックグラウンドタスクを実装するとき に使用 バックグラウンド関連APIの差分を意識しなくてよくなった
WorkManagerのテスト ・ WorkerBuilder(WorkManager 2.1.0+) ・ WorkerFactor ・ Robolectric Support
TestListenableWorkerBuilder val context = ApplicationProvider.getApplicationContext() val worker = TestListenableWorkerBuilder<MyWorker>( inputData
= workDataOf("key" to "value") context = context).build() val result = worker.startWork().get() assertThat(result, equalTo(Result.success()))
TestWorkerBuilder Executorが差し替えられるのがTestListenableWorkerBuilderとの違い val worker = TestWorkerBuilder<MyWorker>( context = context, executor
= executor, inputData = workDataOf("key" to "value")).build() val result = worker.startWork().get() assertThat(result, equalTo(Result.success()))
WorkerFactory 通信処理だったりDBへのアクセスだったりにテストにしにくい要素 に依存することがある WorkerFactoryを利用することで任意のコンストラクタを持つ WorkManagerを作成可能
WorkerFactory 通常のWorkManager val request = OneTimeWorkRequestBuilder<MyWorker>().build() WorkManager.getInstance(context).enqueue(request) class MyWorker( context:
Context, workerParams: WorkerParameters)
WorkerFactory class MyWorkerFactory : WorkerFactory() { override fun createWorker( appContext:
context, workerClassName: name, workerParameters: params) : ListenableWorker? { if (name == MyWorker::class.java.name) { // MyWorker の依存クラスを追加 return MyWorker(context, params, MyDependency()) } return null } }
WorkerFactory Custom Appliction class MyApp : Application() { override fun
onCreate() { super.onCreate() val config = Configuration.Builder() .setWorkerFactory(MyWorkerFactory()).build() WorkManager.initialize(this, config) } } AndroidManifest.xmlでデフォルトのInitializerをremoveする <provider android:name="androidx.work.impl.WorkManagerInitializer" android:authorities="${applicationId}.workmanager‑init" tools:node="remove" />
WorkerFactory @Before fun setUp() { val context = ApplicationProvider.getApplicationContext() val
config = Configuration.Builder() .setWorkerFactory(TestMyWorkerFactory()) .build() WorkManagerTestInitHelper. initializeTestWorkManager(context, config) }
Robolectric Support WorkManagerのテストはLocal Testでも動作する Issueでサポートしない旨の回答があったが、無事に対応された模様 WorkManagerの初期化をしないと実行できなかった @Before fun setUp() {
val context = ApplicationProvider.getApplicationContext() // WorkManagerTestInitHelper をつかう // WorkManager.initialize(context, config) は // Instrumentation Test で実行するとエラーになる WorkManagerTestInitHelper. initializeTestWorkManager(context, config) }
Navigation 画面の遷移の実装をサポート シンプルな遷移から、AppBarやNavigationDrawerとの連携など 画面の遷移をNavigation graphで記述する(フォーマットはXML)
Navigationのテスト ・ with Fragment ・ with Activity
with Fragment FragmentScenarioを使用し、Fragmentにmockの NavigationControllerをセットする Fragmentから別の画面に遷移したときに、NavigationControllerに 正しい引数が渡されているかを見る
with Fragment val navController = mockk<NavController>(relaxed = true) val scenario
= launchFragmentInContainer<TaskFragment>() scenario.onFragment { Navigation.setViewNavController(it.view!!, navController) } ... verify { navController.navigate(TaskFragmentDirections .actionTaskFragmentToTaskListFragment()) }
with Activity 基本的には遷移した後の画面のUIを検証すればいいので、 Navigationを意識する必要ない 内部的にNavigationを使ったActivityScenarioのテストは Robolectric上でも動作する
本日紹介したComponent ・ LiveData ・ ViewModel ・ Room ・ WorkManager ・
Navigation
Summary Architecture Componentはテストのサポートが充実している アップデートにより、書けなかったテストが書けるようになること もある Architecture Component自体の機能も日々アップデートしているの で、あわせてテストはどうなる?も気にかけたい
ご清聴ありがとうございました