Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Android Architecture Componentとテスト2019年7月版 / Te...
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
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.7k
Androidアプリの良いユニットテストを考える / Thinking about good unit tests for Android apps
tkmnzm
5
11k
Google I:O 2023 Androidの自動テストアップデートまとめ / Google I:O 2023 Android Testing Update Recap
tkmnzm
0
730
コルーチンのエラーをテストするための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.6k
SWET dev-vitalチームによるプロジェクトの健康状態可視化の取り組み / SWET dev-vital team's efforts to visualize the health of the project
tkmnzm
1
1.5k
モバイルアプリテスト入門 / Getting Started with Mobile App Testing
tkmnzm
1
670
25分で作るAndroid Lint / Android Lint made in 25 minutes
tkmnzm
0
1.1k
2年半ぶりのプロダクト開発であらためて感じた自動テストの大切さ / realized the importance of automatic testing with product development for the first time in two and a half years
tkmnzm
1
900
Other Decks in Programming
See All in Programming
クラウドゲーミングのパフォーマンス最適化に挑む / Cloud Gaming Performance Tuning
vivion
0
320
あなたの await、OS まで届いてる? strace で覗く JavaScript の非同期
hasutosasaki
1
1.9k
そのデザイントークン、Flutterでも同じ見た目になりますか?
nakamizu
0
300
Lambda durable functionsでHuman in the Loopを楽に…楽に……
har1101
2
270
Crash Auto Pipelines
veronikapj
1
350
Apple Intelligenceとオンデバイス時代の到来
yahir0
0
200
Enforcing Your Architecture with Unit Tests in the AI Era (Swift Connection X)
steliosf
PRO
1
120
baseline으로 마이그레이션, 리팩토링 안전하게 맡기기
fornewid
1
870
TanStack Vue Query と Pinia Colada から学ぶ非同期状態の型設計
aoshi
0
610
私なりのハーネスエンジニアリング ~独自の言語処理系の開発を通して~
seosoft
0
180
Railsのためのパスキー実践入門 〜マルチテナントSaaSでの意思決定〜
utakaha
4
3k
AI時代のIaC入門 〜なぜインフラをコードで管理したいのか?〜 / Introduction to IaC in the AI Era
akihisaikeda
6
1.4k
Featured
See All Featured
Speed Design
sergeychernyshev
33
2.2k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
141
35k
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
380
A Modern Web Designer's Workflow
chriscoyier
699
190k
Faster Mobile Websites
deanohume
310
32k
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
650
Writing Fast Ruby
sferik
630
63k
Are puppies a ranking factor?
jonoalderson
2
4.1k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
123
22k
Embracing the Ebb and Flow
colly
88
5.2k
ラッコキーワード サービス紹介資料
rakko
5
5.7M
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
4
4.4k
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自体の機能も日々アップデートしているの で、あわせてテストはどうなる?も気にかけたい
ご清聴ありがとうございました