Android版に対する「コード改善+テスト導入」の取り組みの紹介Androidこんにちは。SWETの瀬戸(@seto_hi)です。2019年7月下旬からSWETにジョインし、テストが書きにくい設計を改善して自動テストの導入をサポートする取り組みを行っています。Androidアプリの設計の改善を進めてきました。本記事では、2か月間の中で改善した4つの事例について紹介したいと思います。いうタクシーの配車サービスを運営しています。今回リファクタリングを行ったのは、アプリのユーザーが配車依頼から支払いまでを行う、アプリの中心となる画面です。画面の構造は下記のようになっています。常時表示されるActivityと地図Fragmentはシンプルですが、OverlayFragmentの構造がとても複雑な作りとなっています。OverlayFragmentは20種類以上の状態があり、その状態に従って10種類以上のFragmentが差し替わるようになっています。それだけでなく、OverlayFragmentの変更を受けてActivityと地図Fragmentの状態も変更されるので、状態の伝搬や管理が複雑になっています。画面構成の複雑さだけでなく、MOVのユーザーアプリには様々な歴史的経緯があり内部実装は更に複雑になっていました。ActivityとFragmentは見事なFatActivityとFatFragmentとして育っており、修正コストの増加やテストコードの書きづらさといった課題につながっていました。原因を分解し、それぞれについて解決策を考えました。課題1:BaseFragmentと多数のopenメソッドBaseFragmentに空実装のopenメソッドが大量に定義され、各Fragmentはそのメソッドを必要に応じてoverrideしており見通しが悪くなっていました。多くのメソッドはイベントをトリガーとして呼ばれるものだったため、ViewModelにイベント用のLiveDataを用意し、各FragmentでobserveすることによりBaseFragmentへの依存を減らしました。地図の操作イベントはCallback経由で一旦Activityに集約され、再度地図FragmentやOverlayFragmentに分配されていました。Activityに処理を集約する必要は全くなかったので、ViewModelのLiveData経由で地図Fragmentに処理をさせるように変更しました。OverlayFragmentの画面遷移を知らせるイベントもCallback経由で行っていたのですが、NavController.OnDestinationChangedListenerを使い、依存と実装量を減らすことができました。これらの修正によってActivityがFragmentのインスタンスを持つ必要がなくなり、今後不必要な依存を作ってしまう可能性が減りました。課題3:APIコールとCallbacksの実装がActivity/Fragmentにべた書きされているAPIコールとCallbacksの実装がActivity/Fragmentに書かれていたため、Activity/Fragmentの行数が増えるだけでなく、Callback内の処理のテストを書くことが難しくなっていました。従い、設計を変更しました。ViewModelでAPIコールをし、Coroutine化したことでcallbackも少ない行数で実装ができています。architectureに適合するにあたり、今回は3段階のステップを踏みました。Repositoryを作成することでViewModelへの移植をしやすくするCoroutine化でCallback処理の整理をする状態をLiveData化し、ViewModelへ移植する依存を減らしつつ移植することにより、安全な設計変更を行うことができました。改善後のプロダクトコードとテストコードは以下のようになりました。サンプルコードCarRequestRepositoryにはCarRequestを返すgetCarRequest()というメソッドがあります。remoteSource.getCarRequest()}ViewModelではこれを読み込み、LiveDataに保存します。必要であればActivityやFragmentでLiveDataをObserveして値を使うことができます。}}テストこのような設計だとテストも書きやすくなります。CarRequestViewModelのコンストラクタ引数としてCarRequestRespositoryを渡しているため(コンストラクタインジェクション)、モックオブジェクトに差し替えることが容易になります。CarRequestViewModel#loadCarRequest呼んだ際、取得結果をobserverで検知できることを確認するテストは以下のようになります。}}課題4:DialogやDrawerMenuのCallback実装もActivity/Fragmentで行っているこちらも課題3と同様にCallbacksの実装がActivity/Fragmentに書かれていたため、Activity/Fragmentの行数が増えるだけでなく、Callback内の処理のテストを書くことが難しくなっていました。こちらはEnumを利用することで課題を解決しています。サンプルコードまず、メニューの各項目をEnumとして定義します。MOVのアプリではメニューの選択時に新しいActivityを開くようになっているため、Intentの生成メソッドも定義しました。Intent}取り回しを便利にするため、idを元にEnumの要素を取得できるメソッドを定義します。}こういった実装をすることにより、Activity側では以下のように記述するだけで各メニューの選択時の処理ができるようになります。将来的にメニュー項目が増えても、Activity側に修正を入れる必要はありません。}ちなみに、このようにEnumを使った手法はActionMenuやActivityResultにも応用できます。テストenum化することによりテストも書きやすくなります。テストは以下のようになります。}最後に上記のような修正によって、アプリの設計は改善の方向に進み、テストコードも書きやすい状態に変化していきました。このようなアプリの設計改善に限らず、SWETチームでは引き続き自動テストを導入するための取り組みを進めていきます。UIテストハンズオンを実施しましたこんにちは。SWETグループの外山(@sumio_tym)です。JUnit5でUIテストを書こうこんにちは。SWETチームの@zhailujiaです。(1)リンクプライバシーポリシースターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。