개요
회사에서 테스트 코드가 많아지면서 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
이 생성된면 Prettier
, CSpell
, ESLint
, Stylelint
, Test
, Build
, Build SCSS
를 실행하고 있습니다. 이 Action는 다음과 같이 약 25분 정도 소요됩니다.
성능 개선
PR을 생성할 때마다 매번 25분이 소요되는 것은 매우 비효율적입니다. 이번 블로그 포스트에서는 Jest
를 실행하는 Action의 성능을 개선하는 방법에 대해서 알아보겠습니다.
Dependencies Cache
가장 먼저 수행한 것은 yarn install
로 설치되는 Dependencies
를 캐시하는 것 입니다. 이를 통해 Dependencies
를 다시 설치하는 시간을 줄일 수 있습니다.
이 부분은 다른 Acions
에서도 사용할 수 있기 때문에 별도의 Composite Action
로 만들었습니다. Composite Action
에 대해서는 다음 링크를 참고해 주시기 바랍니다.
캐시는 actions/cache
를 사용하여 캐시하였습니다.
actions/cache
공식 문서: https://github.com/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
저희 팀에서는 Yarn
의 3.7.0
버전을 사용하고 있습니다. 그래서 corepack enable
을 추가하고, Yarn 3.7.0
의 캐시 폴더를 가져와서 node_modules
와 함께 캐시하였습니다.
또한 프로젝트가 모노레포이기 때문에 **/node_modules
를 사용하여 하위 프로젝트에 있는 node_modules
도 함께 캐시하였습니다.
모노레포가 아니고, Yarn 3.7.0
을 사용하고 있지않다면, 공식 문서를 참고하여 적절한 설정을 해주시기 바랍니다.
이를 통해 아주 조금 성능이 개선되었습니다.
- Before: 1m 25s
- After: 9s
Actions 분리
Prettier
, CSpell
, ESLint
, Stylelint
, Test
, Build
, Build SCSS
를 모두 한 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분 정도 소요되었습니다.
분리한 후, 이 Action
는 약 13분 정도로 성능이 개선되었습니다.
Jest의 bail
Jest
의 bail
옵션을 사용하면, 테스트 중에 하나라도 테스트가 실패하면 테스트를 중지하도록 설정할 수 있습니다.
이 옵션을 추가하면 테스트가 실패했을 때, 모든 테스트를 실행하지 않고 바로 중지하게 되어 시간을 단축할 수 있습니다.
Jest
의 bail
옵션은 설정하기 위해 jest
를 실행하는 명령어가 포함된 package.json
파일을 열고 다음과 같이 수정합니다.
{
...
"scripts": {
...
"test:ci": "jest --ci --bail"
},
...
}
Jest의 shard 옵션
Jest
의 shard
옵션을 사용하면, 테스트를 병렬로 실행할 수 있습니다. 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
를 사용하고 있습니다.
Turborepo
공식 문서: https://turborepo.com/
이 Action
에서 실행하는 yarn test:ci:service_1
는 turbo test:ci --parallel --filter=service_1
명령어를 실행합니다. 그래서 Jest
의 --shard
옵션을 전달하기 위해 --
를 사용하여 옵션을 전달하였습니다.
shard
옵션을 추가하기전 테스트는 약 13분 정도 소요되었습니다.
shard
옵션을 추가한 후 테스트는 약 2~3분 정도 소요되었습니다.
완료
이번 블로그 포스트에서는 Jest
를 실행하는 Action
의 성능을 개선하는 방법에 대해서 알아보았습니다. 성능 개선전에는 약 25분 정도 소요되었지만, 성능 개선 후에는 약 2~3분 정도 소요되었습니다.
여러분도 Cache
, Actions 분리
그리고 shard
옵션을 통해 Jest
를 실행하는 Action
의 성능을 개선해 보시기 바랍니다.
제 블로그가 도움이 되셨나요? 하단의 댓글을 달아주시면 저에게 큰 힘이 됩니다!
앱 홍보
Deku
가 개발한 앱을 한번 사용해보세요.Deku
가 개발한 앱은 Flutter로 개발되었습니다.관심있으신 분들은 앱을 다운로드하여 사용해 주시면 정말 감사하겠습니다.