Contents
Outline
Recently, the number of test codes has increased in our company, and the Action that checks the code in GitHub Actions
has taken a long time. In this blog post, I would like to share how I improved the performance of the Action that runs Jest
.
Problem
Currently my team uses the following Action to check 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
When a PR
is created, the Prettier
, CSpell
, ESLint
, Stylelint
, Test
, Build
, and Build SCSS
are executed. This Action takes about 25 minutes like the following.
Performance Improvement
GitHub Actions that take 25 minutes every time a PR is created are very inefficient. In this blog post, I will introduce how to improve the performance of the Action that runs Jest
.
Cache Dependencies
The first thing I did to improve the performance of the Action that runs Jest
was to cache the Dependencies
installed by running yarn install
. This reduces the time it takes to reinstall Dependencies
.
This part can be used in other Actions
, so I created a separate Composite Action
. If you want to know more about Composite Action
, please refer to the following link.
I used actions/cache
to cache the Dependencies
.
actions/cache
official document: https://github.com/actions/cache
The Composite Action
that caches Dependencies
using actions/cache
is as follows.
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
Our team uses Yarn
version 3.7.0
. So I added corepack enable
and cached the cache folder of Yarn 3.7.0
and node_modules
together.
Also, since the project is a monorepo, I cached the node_modules
in the subprojects using **/node_modules
.
If you are not using a monorepo and are not using Yarn 3.7.0
, please refer to the official document and make appropriate settings.
- Official document: https://github.com/actions/cache?tab=readme-ov-file#implementation-examples
After caching the Dependencies
, the performance was slightly improved.
- Before: 1m 25s
- After: 9s
Separate Actions
I executed Prettier
, CSpell
, ESLint
, Stylelint
, Test
, Build
, and Build SCSS
in one Action
. Among them, the Test
part took the longest time.
So I separated the Actions
so that other Actions
can be performed while Test
is running.
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
Since we are using a monorepo, GitHub Actions
like this exist for each service, and I used an if
statement to run only the service.
Before separating the Actions
, this Action
took about 25 minutes.
After separating the Actions
, this Action
took about 13 minutes.
Jest bail option
By using the bail
option of Jest
, you can stop the test if one of the tests fails.
By adding this option, you can stop all tests immediately when a test fails, so you can save time.
To set the bail
option of Jest
, open the package.json
file that contains the command to run jest
and modify it as follows.
{
...
"scripts": {
...
"test:ci": "jest --ci --bail"
},
...
}
Jest shard option
By using the shard
option of Jest
, you can run tests in parallel. To run tests in parallel using the shard
option, open the Action
that runs Jest
and modify it as follows.
...
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
This project uses Turborepo
to manage the monorepo.
Turborepo
official document: https://turborepo.com/
The Action
that runs yarn test:ci:service_1
in the Action
is executed by running the turbo test:ci --parallel --filter=service_1
command. So I used --
to pass the option to use the --shard
option of Jest
. So, to pass the shard
option, I used --
.
Before using the shard
option, the test took about 13 minutes.
After using the shard
option, the test took about 2~3 minutes.
Completed
Done! We’ve seen how to improve the performance of the Action that runs Jest
. Before the performance improvement, it took about 25 minutes, but after the performance improvement, it took about 2~3 minutes.
If you have a similar problem, I hope you can improve the performance of the Action that runs Jest
by caching Dependencies
, separating Actions
, and using the shard
option.
Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!
App promotion
Deku
.Deku
created the applications with Flutter.If you have interested, please try to download them for free.