はじめに
こちらはバイセルテクノロジーズのアドベントカレンダー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の成功時のみ)
実行結果
実行結果は次の通りです。
- 各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実行完了時間の短縮
検証結果
検証結果は次の通りです。
- 各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実行結果について、お伝えしたいと思います。
- 各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でのエラーは軽微な修正が多いため、迅速に検知できるようになった
テストの失敗箇所をすぐに特定しやすくなった
まとめ
最後まで読んでいただき、ありがとうございます。
GitHub ActionsのMatrix StrategyとPackage By Featureの特性を活かしたCI高速化戦略について紹介させていただきました。
バイセルは、新卒エンジニアに対しても技術検証、設計や品質改善への取り組みを主体的に行う機会を設けてくれています。
現在、FEエンジニアを募集しておりますので、少しでも興味のある方はぜひご応募ください。
明日の バイセルテクノロジーズ Advent Calendar 2024 は千棒さんの「2024年情シスの業務改善とセキュリティ強化の成果を振り返る」です。 お楽しみに!