バイセル Tech Blog

バイセル Tech Blogは株式会社BuySell Technologiesのエンジニア達が知見・発見を共有する技術ブログです。

バイセル Tech Blog

CI高速化戦略 ~ in Package By Feature ~

はじめに

こちらはバイセルテクノロジーズのアドベントカレンダー2024の21日目の記事です。前日の記事は遠藤さんの「ジョブエクスプローラでBigQueryのボトルネックをリサーチする」でした。

こんにちは!私はテクノロジー戦略本部に所属し、2024年に新卒として入社した高橋です。

2023年の10月から内定者インターンとしての経験を積み、現在は開発3部のCRMチームでフロントエンドエンジニア(以下、FEエンジニア)として働いています。

この度、チーム内で継続的インテグレーション(以下、CI)の速度改善に取り組む機会があり、その内容を共有したく、この記事を執筆することにしました。

少しでもCIの速度にお悩みのFEエンジニアの方々の参考になれば幸いです。

概要

私たちのプロダクトは、開発当初から費用対効果を考慮し、関数に対する単体テストおよび各コンポーネントに対するインタラクションテストを実施してきました。

しかし、機能が増えるにつれてテストケースの数が増大し、CI実行完了時間が長くなるという課題が顕著になってきました。

この課題を解決するために、CIの実行完了時間の短縮を図り、開発者体験の向上と開発費用の削減に努めました。

最終的には、不要なテストケースやストーリーの削除、Jestの機能を活用することでさらなる改善を行いましたが、本記事では主要なアプローチによって得られた結果をお伝えしたいと考えています。

前提

  • 単体テストツールとして、Jestを利用している
  • ディレクトリ構成として、Package By Featureを採用している
  • 各Featureディレクトリ配下にテストファイルを配置している
  • 各Feature間で共有するutilやschemaなどのテストファイルはそれぞれの共有ディレクトリ配下に配置している

以下に、私たちのプロダクトのディレクトリ構成の一部を示します。

例)src直下

├── features
│   └── assessmentSchedule
│       ├── components
│       │   └── AssessmentScheduleHeader
│       │       ├── index.stories.tsx
│       │       ├── index.test.tsx
│       │       └── index.tsx
│       ├── hooks
│       │   └── useMemoBulkCreateForm
│       │       ├── index.test.tsx
│       │       └── index.tsx
│       ├── mappers
│       │   └── toAppraisersOrganizationsResult
│       │       ├── index.test.ts
│       │       └── index.ts
│       ├── schemas
│       │   └── transportationEditForm
│       │       ├── index.test.tsx
│       │       └── index.tsx
│       └── utils
│           └── buildAppraiserNgFlagText
│               ├── index.test.ts
│               └── index.ts
├── hooks
│   └── useKeybind
│       ├── index.test.tsx
│       └── index.tsx
├── libs
│   └── fast-equals
│       ├── index.test.ts
│       └── index.ts
├── mapper
│   └── toTerritoriesResult
│       ├── index.test.ts
│       └── index.ts
├── pages
│   └── index.tsx
├── schemas
│   └── url
│       ├── index.test.tsx
│       └── index.tsx
└── utils
    └── isStartDateBeforeEndDate
        ├── index.test.ts
        └── index.ts

改善前のCI

構成

  • GitHub ActionsのMatrix Strategyを利用し、以下の項目を並列実行

    • lint(Eslintによる静的解析)
    • format(Prettierによるコード整形)
    • test(JestおよびReact Testing Libraryによる単体およびコンポーネントテスト)
    • typecheck(型チェック)
  • ChromaticにストーリーをPublish(前段Jobの成功時のみ)

実行結果

実行結果は次の通りです。

改善前のCI実行結果
改善前のCI実行結果

  • 各Jobの実行結果
結果
lint-typecheck-test-format
- lint 1分40秒
- format 1分45秒
- test 18分23秒
- typecheck 1分16秒
chromatic 5分28秒
  • CI実行完了時間と課金対象時間
結果
Total Duration(CI実行完了時間) 24分29秒
Billable Time(課金対象時間) 32分

CI実行完了までの大半をテストの実行時間が占めていることがわかります。

検証

CIの実行完了時間を短縮するためには、テストを実行している箇所を根本的に改善することが重要だと考えました。

そこで今回は、GitHub ActionsのMatrix Strategyと既存のディレクトリ構成を活用し、新たなアプローチを検討しました。

  • GitHub ActionsのMatrix Strategyの特性

    • max-parallelを指定しない場合、Runnerが実行可能な最大数までJobを並列実行してくれる
  • 既存のディレクトリ構成の特性

    • テストファイルは各Feature配下もしくはそれぞれの共有ディレクトリ配下(各Feature間で共有するutilやschemaなど)に配置されている

以下に、改善前と改善後のコードを示します。

改善前

jobs:
  lint-typecheck-test-format:
    env:
      TZ: Asia/Tokyo
    runs-on: ubuntu-latest
    strategy:
      matrix:
        task: ['typecheck', 'ci:lint', 'ci:format', 'test']
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      〜(省略)〜

      - name: Typecheck, Lint, Format and Test
        run: pnpm run ${{ matrix.task }}

改善後

jobs:
  lint-typecheck-format:
    env:
      TZ: Asia/Tokyo
    runs-on: ubuntu-latest
    strategy:
      matrix:
        task: ['typecheck', 'ci:lint', 'ci:format']
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      〜(省略)〜

      - name: Typecheck, Lint, Format
        run: pnpm run ${{ matrix.task }}

  test:
    env:
      TZ: Asia/Tokyo
    runs-on: ubuntu-latest
    needs: lint-typecheck-format
    strategy:
      fail-fast: false
      matrix:
        # MEMO: features配下にディレクトリが切られたら、以下に追加していく
        task:
          [
            'test ./src/features/communicationManagement',
            'test ./src/features/communicationSearch',
            'test ./src/features/customerSearch',
            'test ./src/features/itemRegistration',
            'test ./src/hooks ./src/libs ./src/schemas ./src/utils',
          ]
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      〜(省略)〜

      - name: Test
        run: pnpm run ${{ matrix.task }}

以下に変更点と期待することを記載します。

  • 変更点

    • testをlint、typecheck、formatと別のJobに分け、失敗時には後続のtest Jobを実行しないように
    • 各featureディレクトリおよび共有ディレクトリでtaskを構成
  • 期待すること

    • 前段のJobが失敗した場合にテストが実行されないようにすることで、課金対象時間の削減
    • CI実行完了時間の短縮

検証結果

検証結果は次の通りです。

改善後のCI実行結果
改善後のCI実行結果

  • 各Jobの時間
結果
lint-typecheck-format
- lint 1分7秒
- format 1分16秒
- typecheck 1分16秒
test
- test(test ./src/features/communicationManagement) 8分45秒
- test(test ./src/features/communicationSearch) 1分42秒
- test(test ./src/features/customerSearch) 6分57秒
- test(test ./src/features/itemRegistration) 1分31秒
- test(test ./src/hooks ./src/libs ./src/schemas ./src/utils) 55秒
chromatic 5分13秒
  • CI実行完了時間と課金対象時間
結果
Total Duration(CI実行完了時間) 16分9秒
Billable Time(課金対象時間) 33分

この結果から、課金対象時間が1分増加したものの、CIの実行完了時間が約8分20秒短縮されたことがわかり、導入する価値があると判断しました。

現在

こちらの検証および導入は半年前に実施したもので、複数の機能が追加された現在のCI実行結果について、お伝えしたいと思います。

現在のCI実行結果
現在のCI実行結果

  • 各Jobの時間
結果
lint-typecheck-format
- lint 1分26秒
- format 1分26秒
- typecheck 1分32秒
test
- test(test ./src/features/appointmentSearch) 1分11秒
- test(test ./src/features/assessmentSchedule) 1分27秒
- test(test ./src/features/communicationManagement) 2分59秒
- test(test ./src/features/communicationSearch) 1分34秒
- test(test ./src/features/customerSearch) 3分37秒
- test(test ./src/features/itemRegistration) 1分23秒
- test(test ./src/hooks ./src/libs ./src/schemas ./src/utils) 1分
chromatic 3分48秒
  • CI実行完了時間と課金対象時間
結果
Total Duration(CI実行完了時間) 9分20秒
Billable Time(課金対象時間) 27分

Total Durationの結果が前回と比べて大幅に削減されていることがわかります。こちらに関しては、不要なテストケースやストーリーの削除、Jestの機能を活用し、日々改善を続けてきた成果となっております。

上記の結果から、機能が増えても、CIの実行完了時間に影響が出ず、課金対象時間が上昇しない構成を実現できていると感じています。

また、以下のような効果も現れ、実施して良かったです。

  • lint、format、typecheckでのエラーは軽微な修正が多いため、迅速に検知できるようになった

    lint、format、typecheckでのエラー
    lint、format、typecheckでのエラー

  • テストの失敗箇所をすぐに特定しやすくなった

    テストの失敗箇所の特定
    テストの失敗箇所の特定

まとめ

最後まで読んでいただき、ありがとうございます。

GitHub ActionsのMatrix StrategyとPackage By Featureの特性を活かしたCI高速化戦略について紹介させていただきました。

バイセルは、新卒エンジニアに対しても技術検証、設計や品質改善への取り組みを主体的に行う機会を設けてくれています。

現在、FEエンジニアを募集しておりますので、少しでも興味のある方はぜひご応募ください。

herp.careers

明日の バイセルテクノロジーズ Advent Calendar 2024 は千棒さんの「2024年情シスの業務改善とセキュリティ強化の成果を振り返る」です。 お楽しみに!