この記事は Calendar for Akerun | Advent Calendar 2021 - Qiita の 11 日目の記事です.
Webエンジニアのohioshirt - Qiitaです。主にAndroidアプリの開発を担当しています。
Androidチームは、2020年に投稿されたHello legacy codes! - フォトシンス エンジニアブログ のようなレガシーコードの課題を抱えていました。
レガシーコード自体については、先の記事で詳しく触れていますので、
本記事ではAndroidチームがそれに向き合いやってきたことをざっと振り返ってみます。
主な取り組み
「コード」に特化するとネタが尽きるのでここではもう少し広く、レガシー体験の改善とします。
リリースビルドの自動化
これまではAndroid Studioで Build > Generate Signed Bundleを実行していました。
つまり手作業です。初めはビルドの度にドキドキしていました。
Play Console APIを直接叩く方法もありますが、
https://github.com/Triple-T/gradle-play-publisherで済ませました。
今ではgit tagを打つことでCircleCIでworkflowが走り、
Google Play Consoleにリリース用のBundleが上がるようになりました。
地味な手作業が減ることに加えて、ビルド環境も個人に依存しなくなり不安要素が減りました。
今後も手作業・属人化した作業から解放されるための取り組みを続けていきます。
私たちの戦いはこれからです。
ユニットテストの改善
Androidチームでは、Pull RequestなどをトリガーにCircleCIでユニットテストを実行するワークフローを組んでいますが、
一時期ユニットテストが途中で落ちる事象が続いていました。
悲しみのメッセージたち
Too long with no output (exceeded 10m0s): context deadline exceeded
The connection attempt hit a timeout after 120.0 seconds (last known process state: STARTED, running: true). This exception might occur when the build machine is extremely loaded.
Process 'Gradle Test Executor 7' finished with non-zero exit value 137
CircleCIのサポートページ「ビルドに設定してあるタイムアウトリミットを超えた場合」に一部のエラーへの対処法が記載されていますが、
そうした一時凌ぎができる問題ではないことが分かったのでテコ入れを行いました。
テスト実行マシンのスペック見直し
CircleCIのドキュメントによれば、
resource-class: large
以上のマシンスペックを指定するのが一般的なようです。
Androidチームではmediumを指定していました。おそらく導入当時はそれで足りていたのだと思います。
迷わずlargeに上げました。ユニットテストの並列化
CircleCIはテストの並列実行をサポートしているため、Androidチームでもこれを導入しました。
ただし、Androidプロジェクトではこのドキュメントのようにファイル毎の実行はできずモジュール毎のテスト実行が必要です。
コンテナ毎のテスト実行対象モジュールはシェルスクリプトで制御、指定します。
実行中のコンテナID(0〜i)は $CIRCLE_NODE_INDEXで取れるので、 n%i の要領で実行対象を割り振れます。
# feature/loginのような構成で置かれているモジュールに対してディレクトリを走査してリストを生成、forループで回して ./gradlew :login:testDebugUnitTest の形で実行する for D in "${SEARCH_DIRS[@]}"; do MODS="$(ls "${D}")" for MOD in $MODS; do MODULES+=("${D}:${MOD}") done done
- JaCoCoを利用したテストカバレッジの表示
ユニットテストのカバレッジもCIで取るようにしました。 https://github.com/arturdm/jacoco-android-gradle-plugin などでサクッとモジュール単位のレポート生成はできたのですが、
CI上で並列実行されるテストレポートをどう結合すべきかが悩みどころでした。
とりあえずの対処としては、ローカルで実行した状態を再現させることにします。
モジュール毎のテストレポートファイルを一時保存し、
テスト完了後に保存したファイルを元の位置に戻してレポートを結合するようにしました。
動作イメージは以下のようなものになります。
# テスト job - run: cp -r --parents ".*/build/reports" /tmp/reports - persist_to_workspace: root: /tmp/reports # テスト の後のjob - attach_workspace: at: /tmp/reports - run: cp -r /tmp/reports/* . - run: ./gradlew レポート結合 - store_artifacts: path: ./build/reports
暫定対処である印象は否めないものの、妙な作り込みが少ないためひとまずはこれで運用しようと考えています。
諸々の改善によりユニットテスト実行時間が20分(時々落ちる)→平均7分前後にまで短縮されました。
カバレッジの表示については、具体的な数字やグラフが見えると達成感を得やすいので、
まだ導入していない方はぜひご検討ください。
今回の導入によりテストコードが薄い箇所が露見したため、これからも改善に取り組んでいきます。
私たちの戦いはこれからです。
Javaで書かれたコードのKotlin化、リファクタリング
新規に実装する時にKotlinを選ぶのは当然のこととして、
機能改修に合わせて既存のJavaコードをKotlinにリファクタリングするなどの改修を行ってきました。
どれだけ変わったのか、MADスコアを算出して1年前のコードと比較しました。
スコアの算出方法はこちらで紹介されていますので、
興味がある方はぜひ自分のプロジェクトでお試しください。
地道な改善を続けた結果、Kotlinコードの割合が大きく変化しました。
機能追加・改修にあまり影響を与えないJavaコードはまだ残っています。
今後もビジネス要求に素早く応えるため、そして自分たちの開発体験をよくするための取り組みを続けていきます。
私たちの戦いはこれからです。
皆さんもこの1年間の成果を振り返ってみてはいかがでしょうか。
株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co
Akerun Proの購入はこちらから akerun.com