はじめに
- github actions で CICD パイプラインを構成して運用している
元々プロジェクト内でテストコードが少なかったのもあり最初の頃は気にする程でもなかったのだが、
テストコードを追加し品質を上げていく過程で気づけば github actions の pull Request 時に走るジョブの実行時間が 20m 弱かかるようになっていた。
github actions の無料枠内での実行時間上限が近づいていたこともあり、パイプラインの高速化対応を行った。
やりたいこと
- github actions のジョブ自体の実行時間を早くしたい。
- github actions の実行時間無料枠を有効的に使いたい。
前提
- React(CRA) + Typescript
- テストフレームワークは Jest
- npm を使っている
状況
プルリク時のジョブ実行時間抜粋
パイプラインの実行時間内訳
- 主に時間がかかっているステップは2つ
github actions 月末での消費量
作業ブランチへの push 時に毎回 20 分もテストを実行させているので、このジョブがプロジェクト全体の利用枠を圧迫していることは明白。
検討した対策
- github actions 上のテストを並列化する
- Jest からめちゃくちゃ早いと噂されている Vitest への移行
-
node_modules
のキャッシュ化 - ブランチとの修正差分に影響ある部分だけテストを実行する
対策を実行
github actions 上のテストを並列化する
- test の実行数を分割して、複数 job での並列実行させることでパイプラインの実行時間を短くする作戦。
- 確かに早くはなるが、job 単位での分割ということは複数の
runs-on
を指定するということになる。 - github actions の実行時間無料枠の消費は job 単位(
runs-on
単位)となるため、パイプラインとしての実行時間は短縮されるが無料枠の消費時間は変わらない。 - 以上から、「
github actions の実行時間無料枠を有効的に使いたい。
」 が満たせないと判断したため、この対応は見送った。
Jest からめちゃくちゃ早いと噂されている Vitest への移行
- 巷でテストの実行が早くなると噂されている Vitest を導入する作戦
- 実際に 388 テストファイルを Jest->Vitest へ移行して、それぞれで同じテストを使って計測してみた
※ どちらも並列実行数 6 で検証
1件のテストをチョイスして単体でのスピード比較
- Enzyme+snapshot React Component テストで比較
Jest
Vitest
- testing-library React Component テストで比較
Jest
Vitest
- API 接続部分の単純な Typescript ファイルのテストで比較
Jest
Vitest
全件分テストを実行してスピード比較
Jest
Vitest
結果
テスト項目 | Jest | Viest |
---|---|---|
Enzyme | 17.44s | 10.01s |
testing-library | 14.81s | 6.00s |
普通のテスト | 7.613s | 3.84s |
全テスト | 277.41s | 451.89s |
- 少数の実行だと Vitest は Jest より早いことが確認できたが、全体で計測したところ Jest の方が早かった。なぜだ・・・
- 結論としては、テスト総実行が Vitest だと遅くなってしまう要因があり、調査も必要だと判断したため導入を見送った
node_modules
のキャッシュ化
- github actions の中で node_modules をキャッシュできるという記事があったので導入を行った
-
actions/setup-node
だけでキャッシュ化ができるという記事が合ったが、キャッシュが効いていなかったのでactions/cache
を併用する
元の github actions step
- name: Setup
uses: actions/setup-node@v2
with:
node-version: "16"
- run: |
npm install
キャッシュ化導入後 github actions step
- name: Setup
uses: actions/setup-node@v2
id: setup_node_id
with:
node-version: "16"
- uses: actions/cache@v3
id: node_modules_cache_id
env:
cache-name: cache-node-modules
with:
path: "**/node_modules"
# package-lock.jsonのハッシュ値をキャッシュ用のkeyとして入れることで、依存関係が変わったらキャッシュを取り直すようになっている
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
- run: echo '${{ toJSON(steps.node_modules_cache_id.outputs) }}'
# キャッシュがあれば、npm install をスキップする
- if: ${{ steps.node_modules_cache_id.outputs.cache-hit != 'true' }}
run: |
npm install
結果
ブランチとの修正差分に影響ある部分だけテストを実行する
-
そもそもプルリク時にコードの 1 部しか修正していないのに、テストの全実行をしていることが非効率なのではないかと気づいた
-
よって、プルリク時のテストは作業ブランチと master との差分に影響するテストのみ実行するようにパイプラインを変える
-
Jest のコマンドに
--changedSince
というオプションがあるRuns tests related to the changes since the provided branch or commit hash.
If the current branch has diverged from the given branch,
then only changes made locally will be tested. Behaves similarly to --onlyChanged.指定したブランチあるいはコミットハッシュからの変更に関連するテストを実行します。
現在のブランチが指定したブランチから分岐している場合は、ローカルで行われた変更のみがテストされます。- プロダクトコードを修正した場合・・・影響するテストが実行される
- テストコードを修正した場合・・・修正したテストが実行される
before
-
github actions step
- name: Checkout uses: actions/checkout@v3 - name: Setup # 省略 - name: Test run: | npm run test:ci
-
package.json
{ "scripts": { "test:ci": "CI=true TZ=Asia/Tokyo react-scripts test --watchAll=false" } }
-
- テスト全部実行なのでそれなりに重い
after
-
github actions step
- name: Checkout uses: actions/checkout@v3 with: # fetch-depth: 0 を設定すると、すべてのブランチとタグのすべての履歴がフェッチされます。 fetch-depth: "0" - name: Setup # 省略 - name: Test run: | npm run test:diff:ci
-
package.json
{ "scripts": { "test:ci": "CI=true TZ=Asia/Tokyo react-scripts test --watchAll=false", "test:diff:ci": "CI=true TZ=Asia/Tokyo react-scripts test --watchAll=false --changedSince origin/master" } }
- ローカルで試すと
--changedSince master
でも動くが、github actions 上だと--changedSince origin/master
としないとエラーが吐かれた- エラー内容
● Test suite failed to run fatal: ambiguous argument 'master...HEAD': unknown revision or path not in the working tree. Use '--' to separate paths from revisions, like this: 'git <command> [<revision>...] -- [<file>...]'
- エラー内容
- ローカルで試すと
-
- 修正内容に影響するテストのみが実行され、実行時間が大幅に短縮された
Vitest でブランチ間差分テストを実行したい場合
- ちなみに vitest で同じようなことをする場合は、
--changed
オプションになる
まとめ
4つ対策として挙げたが、その中で
-
node_modules
のキャッシュ化 - ブランチとの修正差分に影響ある部分だけテストを実行する
取り入れたことで、やりたかったことの
- github actions のジョブ自体の実行時間を早くしたい。
- github actions の実行時間無料枠を有効的に使いたい。
を実現することができた。
プルリク時のジョブ実行時間抜粋
修正量にもよるが 3m〜5m 程度でパイプラインが終わるようになった
パイプラインの実行時間内訳
- 主に時間がかかっているステップは2つ
-
Run cd ./front-app
という step でnpm install
を行うが、キャッシュが有効のため skip された -
Test
で差分実行オプションを付与したため実行時間が短縮された
参考
node_modules
のキャッシュ化
- GitHub Actions で actions/setup-node だけで node_modules をキャッシュできるのか試してみた
- Github Actions で node 環境をセットアップ