Retty Tech Blog

実名口コミグルメサービスRettyのエンジニアによるTech Blogです。プロダクト開発にまつわるナレッジをアウトプットして、世の中がHappyになっていくようなコンテンツを発信します。

レッティ AndroidアプリにおけるCIチェックの体制

Rettyでアプリエンジニアをしている松田(@matsudamper)です。
この記事はRetty Advent Calendar 2024の5日目の記事です。

この記事ではRettyのAndroidアプリのテスト、LintのCIチェック体制がどうなっているかを紹介します。

現在は以下のチェックがCIで走っています。

  • Unit Test
  • Android Lint
  • Instrumented Test
  • ktlint
  • detekt
  • Protocol Buffers Lint/Format

上記のチェックについて、どのような設定、運用が行われているかを紹介していきます。

Unit Test

自分がインターンで入った2019年の頃は、Unit Testは新規の部分には書いていこうという流れがありました。そこから古いコードはどんどん新しく書き直され、新しいコードはテスト前提で書いているので、どんどんテストが書きやすくなっています。
複雑なロジックのコードにテストが書かれているのをはじめ、Robolectricを使用して、ViewModelとCompose UIを合わせたテストが書かれています。

ViewModelとCompose UIを合わせたテストでは、ログのチェックを中心にテストが書かれています。いつの間にかログが呼ばれなくなっていた問題があったのをきっかけに導入され、ログの欠落を防ぐと共に、アプリの基本的な動作を確認する事ができます。テストの実行では、UIの変更ごとにキャプチャを取り、問題が起きた時も、どこのUIがうまく動いていないかを特定する事ができます。特に起きやすいのは、要素が追加され、押したいボタンが画面外に行ってしまってテストが失敗する事です。そういった時にキャプチャを見て、スクロール動作を追加します。

Android Lint

こちらも当初は導入されていなかった為、導入しました。既存のエラーを無視するbaselineファイルを使用し、新規の場所や変更を行う時はエラーが出ないようにしていくというルールで導入しました。
導入当初はbaselineファイルのXMLが42899行ありましたが、今では25859行に減っています。内容は、古いViewのRTLContentDescriptionContentDescription、過去にはGlobal版もあった為、MissingTranslationが多いです。

経験から、warningはバグに繋がる事が多いため、warningsAsErrorsを使用していて、warningでもチェックが通らないようにしています。
必要に応じて@SuppressLintを使用していて、その部分はレビューする人が気を付けて見ています。設定しているので多いのは、SetJavaScriptEnabledMissingPermissionです。MissingPermissionパーミッションチェックの直後に呼べば必要ありませんが、コードの設計上、他の状態と合わせてEnumに変換を行ったりすると通らないので@SuppressLintします。

Instrumented Test

Instrumented TestはCircleCIでエミュレータを動かしていてコストが重い為、リリース時のCIでのみ動かしています。絶対に担保したい機能についてだけこのテストを使用しています。Rettyでは、古いバージョンのアプリを使用できなくする強制アップデートの機能でのみ導入しました。

ktlint

レビューでコードのフォーマットを指摘するのは不毛であり、開発の着手前にIDEAでフォーマットをかけたら差分が出て、プルリクエストの差分が見にくくなるのを防ぐ為に、フォーマッタを導入しました。
Rettyではktlintktlint-gradleで使用しています。

また、.editorconfigで以下のルールを使用しています。
IDEAの設定は以下のようになっています。

# 開発者が見やすいように改行するために、多めに取る
max_line_length = 200

# importの順番の設定
ij_kotlin_imports_layout = android.**,androidx.**,java.**,javax.**,kotlin.**,kotlinx.**,*

# *を使用したimportは使用せず、明確に指定する
ij_kotlin_name_count_to_use_star_import = 9999
ij_kotlin_name_count_to_use_star_import_for_members = 9999
ij_kotlin_packages_to_use_import_on_demand = unset

# 末尾にカンマを必ず入れる。要素を追加した時に差分が1つで済む
ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true

ktlintの.editorconfigの設定は以下のようになっています。
改行等は、開発者が見やすいように設定できるようにオフにしています。

# IDEA準拠のコードスタイル
ktlint_code_style = intellij_idea

# 引数は自分で見やすい改行にする
ktlint_standard_class-signature = disabled
ktlint_standard_function-signature = disabled

# 省略形を使用しない
# fun foo() = "foo"
ktlint_standard_function-expression-body = disabled

# package nameにアンダースコアを使用したほうがまとめやすい
ktlint_standard_package-name = disabled

# Annotationと変数定義を一行で書くのを許可する
# @SerialName("count") val count: Long
ktlint_standard_annotation = disabled

# Compose用の命名を許可
ktlint_standard_function-naming = disabled
ktlint_standard_property-naming = disabled

# listの定義で、ある程度のグループで区切りたい時があるのでdsiable
ktlint_standard_no-blank-line-in-list = disabled

# 右辺の改行は自由にする
ktlint_standard_multiline-expression-wrapping = disabled
ktlint_standard_string-template-indent = disabled

# 関数を連続して書くのを許可する
ktlint_standard_blank-line-before-declaration = disabled

detekt

静的コード解析のdetektでは、Compose Ruleを使用する為に最初に導入しました。Compose特有の命名やルールを強制するために便利です。

他には、detekt-compiler-rulesを導入しています。これは、Kotlinのwarningをerrorにする事ができます。KotlinのオプションにはallWarningsAsErrorsでwarningをerrorにする事ができますが、既存のwarningを無視してコンパイルを通す事ができません。detektではbaseline機能があり、既存のエラーのwarningを許容する事ができます。
こちらも@Suppressで必要に応じてエラーを無視する事ができます。Rettyで設定されているので多いのは、ライブラリ的ななコードで使用されるUNCHECKED_CASTでした。

Protocol Buffers Lint/Format

RettyアプリではProto DataStoreを使用している為、protoファイルを記述します。それのFormatとLintも入れています。こちらに関しては特に複雑な設定を行っていません。

おわりに

以上がRettyのAndroidアプリで使用しているCIチェックでした。参考になれば幸いです。