大規模Ruby on Rails開発における、10万ケース単体テストの高速化とAI活用

はじめに

OPTiM Biz の開発をしております。伊藤です。

本記事は、OPTiM TECH BLOG Advent Calendar 2025 Day 8 の記事です。

OPTiM Bizはモノリシックな Ruby on Rails で開発されており、15年近い歴史を持つサービスです。その歴史の中で、単体テストのケース数は 10万5千ケース まで成長しました。

現在、OPTiM Biz は Ruby 3.4、Ruby on Rails 8.0 で動いています。これまでの度重なるバージョンアップを支えてくれたのはこの膨大な単体テストであり、プロダクトの維持に非常に重要な存在です。 しかし、多くの単体テストを回し続けるのはコストが掛かります。

  • 合計実行時間:17時間35分
  • すべてのマージリクエストで全テストを実行するのは非現実的
  • しかし、開発中は少しでも早くテストによるフィードバックがほしい

この記事では、10万ケース超えの単体テストと向き合い、どのように効率的にテストを実行しているかをご紹介します。

(余談ですが、Ruby 4.0/Ruby on Rails 8.1の対応も現在進めています。先日Bundlerの4.0もリリースされました!試したいです)

OPTiM Bizの単体テスト運用

現在、OPTiM Bizの単体テストの状況は以下のようになっています

  • RSpecで記述されている
  • 合計のケース数は10万5千
  • 単体テストのみで、systemテストやE2Eテストなどは含まない
  • 合計実行時間は17時間35分

工夫していること

これらの単体テストを複数のチーム/エンジニアで実行し続けることはコストが大きすぎると考えています。 テストの待ち時間を減らしたり、実行計画を工夫したりしているので紹介させてください。

工夫1: 50並列実行

まずは並列実行です。GitLab CI の parallel機能 を利用して、50並列 でテストを実行しています。

実行環境は主にGitLab Runnerをk8sでオートスケールするように運用しており、自動的な縮退や安いインスタンスを選ぶなど、様々なコスト削減を行って費用を減らす努力をしています。

# .gitlab-ci.yml のイメージ

rspec:
  stage: test
  retry:
    max: 1
  interruptible: true
  services:
    - name: mysql:latest
  variables:
    GIT_DEPTH: '1'
    RAILS_ENV: test
  cache:
    key:
      files:
      - Gemfile.lock
    paths:
    - vendor/bundle/
  artifacts:
    when: always
    paths:
    - report/rspec.xml
    reports:
      junit:
      - report/rspec.xml
  rules:
  - if: $CI_COMMIT_BRANCH == "master"
  parallel: 50
  script:
  - bundle config set --local path 'vendor/bundle'
  - bundle config set --local with 'test'
  - bundle install --local
  - bundle exec rake db:create db:migrate
  - bundle exec rspec --format RspecJunitFormatter --out report/rspec.xml

これにより、すべてのテストを回す時間は合計の17時間から1時間ほどまで短縮されています。

今後の課題

50並列にしても各ノードでセットアップの時間が長いために時間の短縮効果が限定されてしまっているため、セットアップの時間を短くする活動をしていきたいと思います。 また、50並列という数字は「昔のGitLab CIの最大並列数が50だったから」という理由で最適化されていません。並列数の最適化も進めたいと考えています。

工夫2: 実行ファイルの選定

すべての開発ブランチで10万ケースのテストを実行するのは効率的ではないと考え、マージリクエスト単位では変更箇所などから関連するテストのみを実行する工夫をしています。

この仕組みを提供してくれているのがLaunchableというサービスです。(※Launchableは現在はCloudBeesに買収されたため、ホームページはCloudBeesのものになっています)

LaunchableはRubyの本体にも利用されており、テスト結果をAIで解析し様々な洞察を与えてくれます。

その中でも「Predictive Test Selection」という機能を特に利用させていただいています。この機能は詳しく説明すると複雑なのですが、簡単に言うと「先に実行すべきテストを過去のテスト実行結果を元にAIでリストアップしてくれる」ものです。

# .gitlab-ci.yml イメージ
# テストを開始するジョブ
start-rspec-for-mr:
  stage: build
  variables:
    GIT_DEPTH: '1'
  rules:
  - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  artifacts:
    paths:
    - subset.txt
    - launchable-session.txt
  script:
  - ~/.local/bin/launchable record build --name "${CI_PIPELINE_ID}"
  - ~/.local/bin/launchable record session --build "${CI_PIPELINE_ID}" > launchable-session.txt
  - ~/.local/bin/launchable subset --confidence 90% --build "${CI_PIPELINE_ID}" --split rspec subset.txt

# 50並列でテストを実行するジョブ
rspec-for-mr:
  stage: test
  retry:
    max: 1
  interruptible: true
  services:
  - name: mysql:8.0.31
  variables:
    GIT_DEPTH: '1'
    LAUNCHABLE: enabled
  cache:
    key:
      files:
      - Gemfile.lock
    paths:
    - vendor/bundle/
  artifacts:
    when: always
    paths:
    - report/rspec.xml
    reports:
      junit:
      - report/rspec.xml
  timeout: 10h
  needs:
  - start-rspec-for-mr
  rules:
  - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  parallel: 50
  script:
  - bundle config set --local path 'vendor/bundle'
  - bundle config set --local with 'test'
  - bundle install --local
  - bundle exec rake db:create db:migrate
  - >
      ~/.local/bin/launchable split-subset --subset-id $(cat subset.txt) --bin $CI_NODE_INDEX/$CI_NODE_TOTAL rspec |
      tee /builds/bizweb/optimal_biz_web_admin/target_rspec_files.txt
  - xargs bundle exec rspec --format RspecJunitFormatter --out /builds/bizweb/optimal_biz_web_admin/report/rspec.xml < /builds/bizweb/optimal_biz_web_admin/target_rspec_files.txt

こちらを利用して、マージリクエストでまず実行すべきテストをリストアップしてから並列実行しています。これによってGitLab CI Runnerのリソースを約77%削減することができました。

今後の課題

Launchableはテスト選択だけでなく、テスト改善のための洞察も提供してくれます。

「flakyなテスト」や「実行時間の長いテスト」、「全然失敗しないテスト」などのテスト改善に有効な情報を提供してくれるのですが、それを活かしきれていません。

flakyなテストは現在ほぼ検出されていない(嬉しい!)のですが、実行時間の長いテストや失敗しないテストはしっかり確認して、少しづつでも改善していきたいと思っています。

終わりに

15年の歴史を持つ OPTiM Biz が、どのように10万の単体テストと向き合っているかをご紹介しました。

テストは 書くだけでなく運用していく設計も重要 です。AIによるテスト選択は、開発者体験とテスト品質の両立を可能にしてくれる手段だと思います。

Ruby/Rails のバージョンアップやテスト改善に興味がある方、ぜひ一緒に働きませんか?

www.optim.co.jp