CircleCI™において、モノレポ構成のリポジトリで必要なテストのみ実行する方法

こんにちは。

メディアサービス開発部 Webアプリケーション開発課のフサギコ(髙﨑)です。

Ruby on Railsによるバックエンドの実装運用と、AWSによるサービスインフラの設計構築を中心とした、いわゆるバックエンド方面のテックリードとして働いています。
最近はNext.jsで書かれた運用者向けの管理画面までも自分たちで改修するようになってきました。

本記事ではCircleCIにおいて、モノレポ構成のリポジトリで必要なテストのみ実行するようなconfigファイルの書き方についてお話します。

モノレポ構成のリポジトリでは必要なテストだけを実行したい

複数のソフトウェアが相互に連携して動くようなプロダクトではしばしば、一つのリポジトリをディレクトリ分けしてそれぞれのソフトウェアを配置するモノレポ構成が採られます。

モノレポ構成のリポジトリでは多くの場合、複数のソフトウェアが同時に変更されることは稀です。

しかし、素直に自動テストを設定すると、変更されていないソフトウェアにまで自動テストが実行されてしまいます。 自動テストの意義は変更されたソフトウェアに対して実行してリリース前にバグを検出することですから、変更していないソフトウェアにまで実行してしまうのは無駄と言えます。

モノレポ構成のリポジトリにおいてどのソフトウェアが変更されたか、というのはどのディレクトリのコードが変更されたかと同義ですから、マージ先のブランチ(ベースブランチ)とマージ元のブランチとの間で変更されたファイルを列挙すれば、変更されたソフトウェア、すなわち自動テストを実行するべきソフトウェアが列挙できることになります。

CircleCIのpath-filtering orb

さて、「モノレポ config CircleCI」などとgoogleで検索したときにヒットするそれっぽいCircleCIのドキュメントというと、ダイナミックコンフィグ - CircleCIになるかと思います。

そうするとconfigを動的にファイルとして書き出さなければならないように見えたり、continuation orbなるorb*1の記述が出てきてどうすればいいのかわからん、となると思います。

しかし心配はありません。 マージ先のブランチ(ベースブランチ)とマージ元のブランチとの間で変更されたファイルをもとに後続のワークフローをパラメータを渡しながら起動するpath-filteringというorbが公式で用意されています。

path-filtering Orbを使ったテストの条件付き起動の具体例

個人のGitHubアカウント下ですが、サンプルリポジトリを作成しました。

github.com

CircleCIが最初に読み込む.circleci/config.ymlファイルの内容は下記のようにします。

version: 2.1
setup: true
orbs:
  path-filtering: circleci/path-filtering@1.0.0
workflows:
  trigger_workflows:
    jobs:
      - path-filtering/filter:
          base-revision: main
          mapping: |
            a/.* run_test_A true
            b/.* run_test_B true
          config-path: .circleci/workflow.yml

まず、後続のconfigを起動できるようにするために、setupというキーにtrueを指定する必要があります。

そして、path-filtering/filterジョブの三つのパラメータにそれぞれ下記のような値を指定します。

base-revision
どのファイルが変更されたかを比較するときの比較元ブランチを指定します。多くの場合はmainブランチ、またはdevelopブランチでしょう。
mapping
どのようなpathのファイルが変更されたときに、どのような名前のパラメータにどんな値を設定するかを指定します。
半角スペース区切りで1個目が変更されたファイルのpathにマッチさせる正規表現、2個目がパラメータ名、3個目が設定したい値です。
config-path
mappingにしたがって設定されたパラメータを渡して起動するconfigファイルのpathを指定します。相対パスを書いた時のcurrent directoryはリポジトリrootです。

起動される側のワーククローの内容は下記のようにします。

version: 2.1

parameters:
  run_test_A:
    type: boolean
    default: false
  run_test_B:
    type: boolean
    default: false

jobs:
  test_A_job:
    docker:
      - image: cimg/base:2021.04
    steps:
      - checkout
      - run:
          name: echo test_A
          command: echo 'Hello test_A job!'
  test_B_job:
    docker:
      - image: cimg/base:2021.04
    steps:
      - checkout
      - run:
          name: echo test_B
          command: echo 'Hello test_B job!'

workflows:
  test_A:
    when: << pipeline.parameters.run_test_A >>
    jobs:
      - test_A_job
  test_B:
    when: << pipeline.parameters.run_test_B >>
    jobs:
      - test_B_job

最上位にparametersというキーを定義し、その中にパラメータ名をキーとしてtypeとdefault値を定義します。 そして、各workflowのwhenキーにおいてそれらparameterを参照して、実行の有無を決定します。

また今回のようにsetupワークフローを使用する際にはCircleCIのリポジトリ設定から、Project Settings > Advanced Settings > Enable dynamic config using setup workflows の項目をONにする必要があります。

このようにすれば、

という動作を実現できます。

以前は4つめの全てのpathにもマッチしなかったパターンにおいても後続のconfigが起動された結果実行するワークフローがないというエラーになってしまっていた記憶があります。
そのためどのワークフローも起動されなかった場合に実行する、中身が空のワークフローをNOR条件で記述していたのですが、その挙動はいつのまにか修正されていました。

まとめ

本記事ではCircleCIにおいて、モノレポ構成のリポジトリで必要なテストのみ実行するようなconfigファイルの書き方についてお話しました。

私たちのチームではコーポレートサイト > 事業紹介 > プラットフォーム事業の事業実績に掲載されている一迅社さまの一迅プラスTOブックスさまのコロナEXという二つの漫画連載サイト(サービス開始日時順)の開発保守を担当しています*2

実はこの二つのサイトのバックエンドはコア機能のコードを共通化しており、その共通コードの上にそれぞれ月額課金に関するコードと単話課金に関するコードをadd-onとして載せている形になっています。
つまり、バックエンド単体としても内部に共通コード、一迅プラス向けコード、コロナEX向けコードから成るモノレポを構成していて、そのような場合にもこのCircleCIのpath-filtering Orbを用いた条件付き実行は上手く対応できています。

…と、この記事を書き終えたところでCircleCIのドキュメントにまさにこれに関する記事が存在しているのを発見してしまいましたが、せっかく書いたし弊社ではこれを実用しています、ということで公開したいと思います。

ブックウォーカーでは物理・電子・Web連載問わず漫画や本が好き、あるいは将来の電子書籍や漫画連載をより良い設計ならびに実装で実現していくことに興味があるWebアプリケーションエンジニアを募集しています。

興味がありましたらぜひ、下記採用情報ページからカジュアル面談をお申し込みください。 お待ちしています。 www.bookwalker.co.jp

*1:CircleCIのジョブを使いまわせるしくみ

*2:少々ややこしいことに、コーポレートサイト上ではプラットフォーム事業の欄に掲載されていますが、私たちのチームはメディア事業所属です