自己紹介
- 三輪 智也(みわ ともや) @tomoya0x00
- Android&組み込みエンジニア
- 株式会社ディー・エヌ・エー(11/16入社)
- DroidKaigi2018では、Kioskネタで登壇させていただきました
状況説明
- プレビュー版なアプリをベースに正式リリースへ向けて改修中
- フルKotlin
- DIはDagger2
注意
- テストコードあんまり書いたこと無いです
- 特にKotlinでテストコード書くのは初めて・・・
- ほとんどDagger使った事ありません
なぜ登壇することになったしたのか?
テスト書いていて、つい調子に乗ってしまった
※TestNight前日は、住居のお引っ越し日です(今日、転入届出してきた)
事例1:新規作成のStateMachine
事例1:新規作成のStateMachine
- 既存アプリにStateMachineを導入したくなった
- フラグでの条件分岐が増えてきていてツラい
- 意図しないタイミングのイベントによる想定外の画面遷移を防止するため
- 影響範囲が広い&網羅的に動きを見たかったので、
TDDで実装していくことにした
どうやって
- StateMachineへの依存性注入は古典的なコンストラクターで全部渡すタイプにした
Daggerを避けた
- Android依存は切り離して、Local Unit Test(実機・エミュ無しで動くユニットテスト)にした
- 事前に状態遷移図書いて、各Stateごとにテスト書く->コード実装を繰り返していった
- mockは nhaarman/mockito-kotlin
- assertionsは willowtreeapps/assertk
手間取ったこと
- mockする既存classとfunをopenにすること
- Kotlinだと、デフォルトfinalなので
- interface切ればすっきりだけど、全クラスにそれをするのか・・・?
実際にやってみて
- すごくコスパ良かった
- テストを先に書くことで検討漏れに気付きやすかった
- 実機でわざわざ動かさなくて良いので、さくさくコーディング&テストできた
- おかげで、アプリにStateMachine組み込んでほぼほぼ一発で一通り動いた
テストサイコー
次の事例
事例2:既存クラスの作り直し
事例2:既存クラスの作り直し
- 要件上、不揮発領域へのデータ保存が必要になったので、Room導入
- 既存クラス自体がステートを持っていたので、メソッドの引数を見直してステートレスに変更
困ったこと
- RoomはLocal Unit Test非推奨
- 既存クラスがDaggerでフィールド・インジェクションしていて、漏れなく依存性注入し辛そう
- 既存クラスが依存しているクラスもDaggerでフィールド・インジェクションしていて、依存性注入し辛そう
RoomはLocal Unit Test非推奨
- 公式ドキュメントでLocal Unit Testは非推奨となっている
- しかし、Roomに依存させる既存クラスごとInstrumented Unit Test(実機・エミュで動かすユニットテスト)にするのは避けたい
- 実機なしでもお手軽に実装したかったので
- RoomのDAOだけInstrumented Unit Testにして、既存クラスへはDAOのmockを注入してLocal Unit Testを実行できるようにした
- 公式ドキュメントでも、DAOのmockについて触れられている
既存クラスがDaggerでフィールド・インジェクションしていて、漏れなく依存性注入し辛そう
- 全部mockしても良いんだけど、「これはメソッドに引数で渡した方が良いのでは?」というのも、フィールドインジェクションしていた
- あと、Toastだすためだけにcontext注入していたり
- 本当に必要な依存だけに絞って、依存を 5->1 + 1(Room)
に削減- ほとんどは、メソッドの引数で渡す事にした
- mockを漏れなく注入しやすいように、コンストラクター・インジェクションに変更
既存クラスが依存しているクラスもDaggerでフィールド・インジェクションしていて、依存性注入し辛そう
- Dagger力が足りずにすごくハマった
-
現実世界でのAndroid + Dagger2によるテスト
と How do you override a module/dependency in a unit test with Dagger 2.0?
をとても参考にさせて頂いた- mockをprovideするTestAppModule、 TestAppComponent(AppComponentを継承)を用意
- Application継承クラスにAppComponent入れ替え用の
fun setTestComponent
を用意して、ユニットテスト時はTestAppComponentに入れ替える
実際にやってみて
- 事例1に比べてコスパが良くなかった
- 主にDaggar力の足りなさで、ユニットテストを動かすのに時間がかかった
- 既存クラスで、引数で良さそうなものまでDIしていたのを修正したりもしていたので・・・
- もちろん、テスト書いたおかげて見つけられた不具合もあった
- 今後は今回の経験を元に、他の既存クラスもテストしやすく変更していけそう
感想
- 新規もの&Androidに依存しない場合はテスト書きやすい&コスパ良いので、ぜひテストコード書きましょう!
- フィールド・インジェクションよりはコンストラクター・インジェクションの方がテストしやすい印象
皆様に聞いてみたいこと
- KotlinだとJaCoCoでカバレッジ取れない・・・?
- mockするのにわざわざ既存クラスやメソッドをopenにしている?それとも、毎回interface切っているんでしょーか