UPSIDER Tech Blog

KotlinアプリのCIを並列化して実行時間を27%短縮した話

はじめに

こんにちは。UPSIDERのカード事業でバックエンド開発を担当している三井です。

これは、UPSIDER Tech アドベントカレンダー 2025の1日目の記事です。

UPSIDERのアドベントカレンダー2025 では、Tech・Corporate・Bizの3つに分かれて、それぞれのチームメンバーが日替わりでさまざまな内容をお届けします。

Techはこちら: UPSIDER Tech Advent Calendar 2025 - Adventar
Corpはこちら: UPSIDER Corp Advent Calendar 2025 - Adventar
Bizはこちら: UPSIDER Biz Advent Calendar 2025 - Adventar

今までKotlinアプリのCIが16分程度かかっていて、遅く感じていました。
普段の開発時やデプロイ、緊急時の対応にCIで待機が発生することがあり、全体のスピード感が落ちていました。
この記事では、一般的なKotlinアプリのCI環境でも実践できる「CIのjobの並列化」による高速化の手法を紹介します。

要約

  • CIのjobを並列化して、各テストグループを複数のjobで同時に実行するようにした。
  • その結果、全体の処理時間は 16分4秒 → 11分40秒 に短縮でき、27%の短縮に成功した。

CI高速化の結果

こちらがCIのbefore, afterです。全体の処理時間は16分4秒 → 11分40秒に短縮することができました。

before

after

方針

以下、CI高速化における技術選定やCIのjobの並列化についてです。

CI高速化の技術選定

今回はCIのjobの並列化を行いましたが、他にもいくつかの選択肢がありました。

  • JUnit / Kotest の並列実行 x Testcontainers の導入

  • runnerのリソース増強

ただ、統合テスト用のdockerのDBが各テストで共用で利用されていたり、固定のfixtureに強く依存していたため、短期的にはCIジョブの並列化が最も手軽でした。 また、runnerのリソースについてはすでに大きめのものを利用していたため、コスト的に断念しました。

CIのjobの並列化

Gradleによるテストでは、以下のように複数のテストクラスを個別に指定して実行することができます。

./gradlew test --tests com.example.AaaTest --tests com.example.BbbTest

これまでCIではすべてのテストをまとめて実行していましたが、渡すテストクラスを分割して指定することで並列実行が可能になります。

CIのjobの実装

以下のように matrix を使って複数のグループに分割し、同時に実行できるようにしました。

strategy:
  matrix:
    test-group:
      - name: "Group 1"
        pattern: 0
      - name: "Group 2"
        pattern: 1
      - name: "Group 3"
        pattern: 2
      - name: "Group 4"
        pattern: 3

次に、テストクラスを4分割して各ジョブで処理できるようにしました。 下記のコマンドでは、テストクラスの .class ファイルを列挙し、awk で行番号ごとに分割して実行します。

TEST_ARGS=$(
  find build/classes/kotlin/test -name "*.class" \
  | grep -v "\$" \
  | sort \
  | awk 'NR % 4 == ${{ matrix.test-group.pattern }}' \
  | while read f; do
      echo "--tests $f" \
      | sed 's|build/classes/kotlin/test/||' \
      | sed 's|/|.|g' \
      | sed 's|.class$||'
    done \
  | tr '\n' ' '
)
eval $(echo "./gradlew test $TEST_ARGS")

上の処理では、以下のような流れでテストを割り振っています。

  1. .class ファイルをすべて取得
  2. 内部クラス($を含むファイル)は除外
  3. sort で安定した順序に並べる
  4. awk で行番号に応じてグループ分割
  5. クラスパスをドット区切りに整形し、--tests オプションに変換

こうすることで、テストを4つのジョブに均等に分配して並列に実行できるようになりました。

job並列化の壁

並列化を行うと既存のテストに影響がありました。 統合テストでは各テストで統合テスト用のDBを共用しており、テストクラスの中でDBに書き込んだりfixtureを読み込んだりする処理が多くあります。他のテストで追加・更新済みのデータを使ってしまっているテストもあります。これまでは逐次実行だったため問題になりませんでしたが、今回の並列化でそのようなテストは落ちるようになりました。 幸い、そのような影響は数件程度で済みました。

テストの正確性確認

並列化によって、テストの列挙やグループ分けの処理が正しく動作しているかを確認する必要があります。 テスト実行後に build/reports/tests/test/index.html を開いて出力されたレポートを確認しました。 ここでテストがカバーされていることを確認してから、運用に移行しました。

おわりに

並列化によってスピードは確かに上がりました。他メンバーも体感するほど待ち時間が減り、CI確認のテンポも良くなりました。 今回は現状の統合テストの仕組みを維持したままの改善でしたが、今後は他のアプローチも試していきたいと思っています。

UPSIDERアドベントカレンダー2025

株式会社UPSIDERのメンバーがお届けする、#UPSIDERアドベントカレンダー2025。Biz・Tech・Corporate の3つに分かれて、それぞれのチームメンバーが日替わりでさまざまな内容をお届けします。内容は仕事に限らず、日々の学びや経験、好きなこと、趣味の話、ふと思ったこと など、自由に…!UPSIDERで働くメンバーの “素顔” が垣間見えるような、そんな企画になっています。気軽に読みながら、メンバーそれぞれの雰囲気を感じていただけたら嬉しいです。

adventar.org adventar.org adventar.org

We Are Hiring !!

UPSIDERでは現在積極採用をしています。 ぜひお気軽にご応募ください。

herp.careers

herp.careers

UPSIDER Engineering Deckはこちら📣

speakerdeck.com