(Deprecated) Advanced Android in Kotlin 05.2: Introduction to Test Doubles and Dependency Injection
次やるのはこれです。
前回のCodeLab 05.1 の答えが実装されている状態となるので、以下のリポジトリ・ブランチを拝借して取り組む
とりあえず順番に内容を見ていく!
3. Concept: Testing Strategy
テストコードを実装するにあたっての戦略的内容。
Unit Test
Integration Test
E2E Test
のピラミッドは他のテストに関する記事でも頻繁に説明される内容だよね。そしてこれらのテストコードが適切に実装できるかどうかは、アプリのアーキテクチャにかかっているというのもよくある話。
- エンジニアは安心して修正・変更したい
- (でも動作確認は面倒)
- テストコードが書ける環境でいたい
という経緯がアーキテクチャ(MVC,MVP,MVVM)やクリーンアーキテクチャの概念, TDDやDDDが話題に上がってきたひとつの要因と認識している。
End to end testing will be covered in the next lesson.
頼むで。
4. Task: Make a Fake Data Source
Fakeなデータソースを用意して、そっちを参照させよう。という話。
少なくても Unit Test
は外部との機能に依存させたくないので、Fake
Mock
を使ってテスト対象からは外部機能にアクセスしているけど、実際にはテスト用のデータを返すようにすることで、依存関係を気にせず本当にテストしたい対象だけをテストする、という狙い。
(これができるのはアーキテクチャ通りに実装できている、という前提があるんだけどね。)
一旦Fakeなデータソースを用意する方針で作業が進みそう。
(あー mockito
使いたい...)
5. Task: Write a Test Using Dependency Injection
ここからは、前の工程で作った Fake
なデータソースを Repository
に置き換える(注入する)ことでテストコードを実行するときは外部機能に依存しないようにする方針かな。
うひゃー。DataSourceの初期化がinitで実装されているのかー!Remoteのほうも直接参照しているし、悲しみの依存関係。。。で、これを直しつつ、って感じかな。
Step 3: Write DefaultTasksRepository getTasks() Test
suspend
関数によるテストをするときの解消する内容。
さて dependencies に追記する時間... これのせいでビルドが通らなくなるかもだから不安だわ。
案の定うまくいかなかったので、 ここだけ答えを見てバージョンを整える。
そして runBlockingTest
が deprecatedされてて悲しみが垣間見える
6. Task: Set up a Fake Repository
今度は ViewModel
に対して FakeなRepositoryを注入することで ViewModel
の外部に依存しないテストコードを実装する内容。
で、そのためには実クラスに依存させないよう定義する必要がある。つまり、DefaultTaskRepository
に依存させないよう定義する必要がある。DefaultTaskRepository
に依存するとその実装に依存してしまうわけで、それにより外部に依存することなるから。
ではどうするかというと、 interface
として定義してあげる。って話。クリーンアーキテクチャの話題になるとよく出てくる右下の矢印(依存関係)の話。
For your test double, use runBlocking. runBlocking, which is a closer simulation of what a "real" implementation of the repository would do, and it's preferable for Fakes, so that their behavior more closely matches the real implementation.
When you're in test classes, meaning classes with @Test functions , use runBlockingTest to get deterministic behavior.
runBlocking
は テストダブル(偽装する側の実装)で、 runBlockingTest
はそれを呼び出すテストコード側でそれぞれ使うのが良いとのこと。
8. Task: Launch a Fragment from a Test
お、Fragment来たな。ここからだ。
Add the following gradle dependencies.
junit:junit—JUnit, which is necessary for writing basic test statements.
androidx.test:core—Core AndroidX test library
kotlinx-coroutines-test—The coroutines testing library
androidx.fragment:fragment-testing—AndroidX test library for creating fragments in tests and changing their state.
ライブラリの追加が必要らしい。
Launch a fragment from a test
Creates a task.
Creates a Bundle, which represents the fragment arguments for the task that get passed into the fragment).
The launchFragmentInContainer function creates a FragmentScenario, with this bundle and a theme.
なるほど。 navigation
を使って Fragment
の遷移処理を実装していると 引数 が渡せないから
launchFragmentInContainer
を使ってスタイルと引数を指定しつつFragmentを呼び出すのか。
Your test both needs to load up the TaskDetailFragment (which you've done) and assert the data was loaded correctly. Why is there no data? This is because you created a task, but you didn't save it to the repository.
おおん? ってそうか。 Task
オブジェクトは作ったものの、実際に渡しているのは id
だけなのか。
( Task
まるごと渡せばいいじゃん。と思ってしまうのは心にしまっておく)
9. Task: Make a ServiceLocator
ViewModel
での Repository
の依存関係を解消するために Repository群を管理する ServiceLocator
を追加する内容。
Fragment
で ViewModel
を Factory.create
してもらいたいので、Fragment側から参照できる Application
に参照口を用意して、そこからViewModelに注入していく流れ。
Step 4. Prepare your ServiceLocator for Tests
Mark the setter for tasksRepository as @VisibleForTesting. This annotation is a way to express that the reason the setter is public is because of testing.
あー。あくまでテスト用であることを 表現 するだけで、setterを秘匿することはできないのか。。
後続のステップを確認した。
ServiceLocatorを使ったテスト時の依存関係の解消方法が学べた。
特に、Espressoを使ったUIテストにおいても、VieeModelが参照するリポジトリをFakeなものに差し替えておけば、特定のケースで期待した画面表示になるかの確認ができるとわかった。
UIテストにおいて、ServiceLocator実装するのは正直しんどいので、そこらへんをHilt(Dagger)に頑張ってもらうのが良さそうってのが今の流行りなのかな。