[GitHub Actions] Jestの実行Actionsのパフォーマンスを改善する

2024-11-11 hit count image

GitHub ActionsでJestを実行するActionのパフォーマンスを改善する方法について紹介します。

概要

最近、会社でテストコードが増えてきたため、GitHub ActionsでコードをチェックするActionに時間がかかるようになりました。この問題を解決するために、Jestを実行するActionのパフォーマンスを改善した内容を共有したいと思います。

問題点

現在、次のようなActionを使用してPRをチェックしています。

jobs:
  check-code:
    name: Check Code
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Install dependencies
        uses: ./.github/actions/install_dependencies
      - name: Prettier
        run: yarn format
      - name: CSpell
        run: yarn cspell
      - name: ESLint
        run: yarn lint
      - name: Stylelint
        run: yarn stylelint
      - name: Test
        run: |
          yarn test:ci
          yarn test:storybook
      - name: Build
        run: |
          yarn build
      - name: Build SCSS
        run: |
          yarn typegen:scss

          # Get the changed files
          CHANGED_FILES=$(git diff --name-only HEAD)
          # Check if there are changes in the generated files
          if [ -n "$CHANGED_FILES" ]; then
            echo "Error: There are changes in the following files: $CHANGED_FILES"
            exit 1
          fi

PRが作成されるたびにPrettierCSpellESLintStylelintTestBuildBuild SCSSが実行されています。このActionは約25分かかります。

GitHub Actions - Improve Jest test performance

性能改善

PRを生成するたびに25分かかるのは非常に非効率的です。このブログポストでは、Jestを実行するActionのパフォーマンスを改善する方法について説明します。

Dependencies Cache

一番最初に行ったのは、yarn installでインストールされるDependenciesをキャッシュすることです。これにより、Dependenciesを再インストールする時間を短縮できます。

この部分は他のActionsでも使用できるため、Composite Actionとして作成しました。Composite Actionについては、次のリンクを参照してください。

キャッシュはactions/cacheを使って行いました。

actions/cacheを使ってDependenciesをキャッシュするComposite Actionは次のようになります。


name: 'Install Dependencies'
description: 'Install Dependencies'
runs:
  using: 'composite'
  steps:
    - name: Setup node
      uses: actions/setup-node@v4
      with:
        node-version: 20.3.0
    - name: Install dependencies
    - name: Enable Yarn 3.7.0
      shell: bash
      run: corepack enable
    - name: Get yarn cache directory path
      shell: bash
      id: yarn-cache-dir-path
      run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
    - uses: actions/cache@v4
      id: yarn-cache
      with:
        path: |
          node_modules
          **/node_modules
          ${{ steps.yarn-cache-dir-path.outputs.dir }}
        key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
        restore-keys: |
          ${{ runner.os }}-yarn-
    - name: Install dependencies
      if: steps.yarn-cache.outputs.cache-hit != 'true'
      shell: bash
      run: yarn install --frozen-lockfile

私たちのチームはYarn3.7.0バージョンを使っています。そのため、corepack enableを追加し、Yarn 3.7.0のキャッシュフォルダを取得してnode_modulesと一緒にキャッシュしました。

また、プロジェクトがモノレポであるため、**/node_modulesを使ってサブプロジェクトのnode_modulesもキャッシュしました。

もし、皆さんのプロジェクトがモノレポではなく、Yarn 3.7.0も使っていない場合は、公式ドキュメントを参照して適切な設定を行ってください。

この変更により、パフォーマンスが少し向上しました。

  • Before: 1m 25s
GitHub Actions - Before using actions/cache for dependencies
  • After: 9s
GitHub Actions - After using actions/cache for dependencies

Actions分離

PrettierCSpellESLintStylelintTestBuildBuild SCSSをすべて1つのActionで実行していました。その中で最も時間がかかったのがTestの実行でした。

なので、Testが実行される間に他のActionsを実行できるようにActionsを分離しました。

jobs:
  cspell:
    name: CSpell
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Install dependencies
        uses: ./.github/actions/install_dependencies
      - name: CSpell
        run: yarn cspell
  remark:
    if: contains(github.head_ref, 'service_1') || contains(github.head_ref, 'npm_and_yarn') || contains(github.head_ref, 'github_actions') || contains(github.head_ref, 'components') || contains(github.head_ref, 'config') || contains(github.head_ref, 'common')
    name: Remark-lint
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Install dependencies
        uses: ./.github/actions/install_dependencies
      - name: Remark-lint
        run: yarn remark:service_1
  eslint:
    if: contains(github.head_ref, 'service_1') || contains(github.head_ref, 'npm_and_yarn') || contains(github.head_ref, 'github_actions') || contains(github.head_ref, 'components') || contains(github.head_ref, 'config') || contains(github.head_ref, 'common')
    name: ESLint
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Install dependencies
        uses: ./.github/actions/install_dependencies
      - name: ESLint
        run: yarn lint:service_1
  stylelint:
    if: contains(github.head_ref, 'service_1') || contains(github.head_ref, 'npm_and_yarn') || contains(github.head_ref, 'github_actions') || contains(github.head_ref, 'components') || contains(github.head_ref, 'config') || contains(github.head_ref, 'common')
    name: Stylelint
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Install dependencies
        uses: ./.github/actions/install_dependencies
      - name: Stylelint
        run: yarn stylelint:service_1
  build:
    if: contains(github.head_ref, 'service_1') || contains(github.head_ref, 'npm_and_yarn') || contains(github.head_ref, 'github_actions') || contains(github.head_ref, 'components') || contains(github.head_ref, 'config') || contains(github.head_ref, 'common')
    name: Build
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Install dependencies
        uses: ./.github/actions/install_dependencies
      - name: Build
        run: yarn build:service_1
  test-service_1:
    if: contains(github.head_ref, 'service_1') || contains(github.head_ref, 'npm_and_yarn') || contains(github.head_ref, 'github_actions') || contains(github.head_ref, 'components') || contains(github.head_ref, 'config') || contains(github.head_ref, 'common')
    name: Test service_1
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Install dependencies
        uses: ./.github/actions/install_dependencies
      - name: Test
        run: yarn test:ci:service_1

モノレポを使っているため、このようなGitHub Actionsはサービスごとに存在し、if文を使ってそのサービスだけが実行されるようにしています。

分離する前には、このActionは約25分かかりました。

GitHub Actions - Before sperating actions

分離した後、このActionは約13分程度で性能が改善されました。

GitHub Actions - After sperating actions

Jestのbail

Jestbailオプションを使うと、テスト中に1つでもテストが失敗した場合にテストを中止するように設定できます。

このオプションを追加することで、テストが失敗した場合にすぐにすべてのテストを実行せずに中止することができ、時間を短縮できます。

Jestbailオプションを設定するために、jestを実行するコマンドが含まれるpackage.jsonファイルを開いて、次のように変更します。

{
  ...
  "scripts": {
    ...
    "test:ci": "jest --ci --bail"
  },
  ...
}

Jestのshardオプション

Jestshardオプションを使うと、テストを並列で実行できます。shardを使ってテストを並列で実行するために、Jestを実行するActionを開いて次のように変更します。

  ...
  test-service_1:
    if: contains(github.head_ref, 'service_1') || contains(github.head_ref, 'npm_and_yarn') || contains(github.head_ref, 'github_actions') || contains(github.head_ref, 'components') || contains(github.head_ref, 'config') || contains(github.head_ref, 'common')
    name: Test service_1
    runs-on: ubuntu-latest
    strategy:
      matrix:
        shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    timeout-minutes: 30
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - name: Install dependencies
        uses: ./.github/actions/install_dependencies
      - name: Test
        run: yarn test:ci:service_1 -- --shard=$/10

このプロジェクトでは、モノレポを管理するためにTurborepoを使っています。

このActionで実行するyarn test:ci:service_1turbo test:ci --parallel --filter=service_1コマンドを実行します。そのため、Jest--shardオプションを渡すために--を使ってオプションを渡しました。

shardオプションを追加する前のテストは約13分かかりました。

GitHub Actions - After sperating action

shardオプションを追加した後、テストは約2~3分かかりました。

GitHub Actions - Use Jest shard option

完了

このブログポストでは、Jestを実行するActionのパフォーマンスを改善する方法について説明しました。パフォーマンス改善前には約25分かかりましたが、パフォーマンス改善後には約2~3分かかるようになりました。

皆さんもCacheActions分離shardオプションを使ってJestを実行するActionのパフォーマンスを改善してみてください。

私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!

アプリ広報

今見てるブログを作成たDekuが開発したアプリを使ってみてください。
Dekuが開発したアプリはFlutterで開発されています。

興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。

Posts