変更に影響したテストファイルだけjest実行してCIの時間を大幅に短縮させた
こんにちは!CastingONEの大沼です。
始めに
CIでデグレチェックのために単体テストを実行すると思いますが、テストファイルが増えるほど実行時間が延びてしまう問題があります。全体に影響を与えるものであればshardを使った並列実行でリアル時間を短くさせることはできますが、局所的な変更の場合は影響のないテストも実行するのは勿体なく感じます。変更したファイルに影響があるテストファイルだけテストするのが理想で色々模索していましたが、dependency-cruiserを使うことで実現することができました!
この記事ではどのようにして変更に影響したテストファイルだけを抽出し、それをテストしたかについて説明したいと思います。
dependency-cruiserを使った依存関係の抽出方法
dependency-cruiserは、ファイルの依存関係を確認するためのCLIツールです。ファイルの依存関係をグラフに表したり、循環参照を検知したりと色々できますが、今回はreachesオプションを使って特定のファイルに到達する依存関係をテキストで出力したものを使います。
具体例
イメージがつきやすいように、例としてAlertDuplicateEmailJobSeekers.tsxとJobSeekerCard.tsxファイルを編集し、これらのファイルに依存したものを抽出する場合で説明したいと思います。
ファイルの依存関係が分かりやすいように先にグラフで出力して抜粋したものを以下に示します。拡大しないと文字が読みづらいですが、緑色でハイライトされているAlertDuplicateEmailJobSeekers.tsxとJobSeekerCard.tsxを起点に依存のグラフが構築されていることが分かると思います。このグラフから、最終的にJobSeekerCard.test.tsxがテスト対象として抽出できると良さそうです。

それでは実際にテキストで出力してみたいと思います。コマンドは以下のようになります。reachesオプションは正規表現で設定するため、|を使って複数ファイルの指定しています。ここでは例としてファイル名のみとしていますが、他のファイルに誤ってマッチしないように実際はsrc/features/comEmail/components/AlertDuplicateEmailJobSeekers/AlertDuplicateEmailJobSeekers.tsxみたいにフルパスで指定するようにしています。
npx depcruise src --reaches 'AlertDuplicateEmailJobSeekers.tsx|JobSeekerCard.tsx' -T text
これの実行結果が以下です。出力されたテキストから.test.tsxで検索かけると分かると思いますが、src/features/comEmail/components/JobSeekerCard/JobSeekerCard.test.tsxがヒットしたと思います。
src/features/comEmail/components/AlertDuplicateEmailJobSeekers/AlertDuplicateEmailJobSeekers.stories.tsx → src/features/comEmail/components/AlertDuplicateEmailJobSeekers/AlertDuplicateEmailJobSeekers.tsx
src/features/comEmail/components/AlertDuplicateEmailJobSeekers/index.ts → src/features/comEmail/components/AlertDuplicateEmailJobSeekers/AlertDuplicateEmailJobSeekers.tsx
src/features/comEmail/components/JobSeekerCard/JobSeekerCard.stories.tsx → src/features/comEmail/components/JobSeekerCard/JobSeekerCard.tsx
src/features/comEmail/components/JobSeekerCard/JobSeekerCard.test.tsx → src/features/comEmail/components/JobSeekerCard/JobSeekerCard.tsx
src/features/comEmail/components/JobSeekerCard/index.ts → src/features/comEmail/components/JobSeekerCard/JobSeekerCard.tsx
src/features/comEmail/components/JobSeekerList/JobSeekerList.stories.tsx → src/features/comEmail/components/JobSeekerList/JobSeekerList.tsx
src/features/comEmail/components/JobSeekerList/JobSeekerList.tsx → src/features/comEmail/components/JobSeekerCard/index.ts
src/features/comEmail/components/JobSeekerList/index.ts → src/features/comEmail/components/JobSeekerList/JobSeekerList.tsx
src/features/comEmail/components/index.ts → src/features/comEmail/components/AlertDuplicateEmailJobSeekers/index.ts
src/features/comEmail/containers/EmailCommunicatedJobSeekersContainer/EmailCommunicatedJobSeekersContainer.tsx → src/features/comEmail/components/JobSeekerCard/index.ts
src/features/comEmail/containers/EmailCommunicatedJobSeekersContainer/EmailCommunicatedJobSeekersContainer.tsx → src/features/comEmail/components/JobSeekerList/index.ts
src/features/comEmail/containers/EmailCommunicatedJobSeekersContainer/index.ts → src/features/comEmail/containers/EmailCommunicatedJobSeekersContainer/EmailCommunicatedJobSeekersContainer.tsx
src/features/comEmail/containers/EmailCommunicationAreaContainer/EmailCommunicationAreaContainer.tsx → src/features/comEmail/components/AlertDuplicateEmailJobSeekers/index.ts
src/features/comEmail/containers/EmailCommunicationAreaContainer/index.ts → src/features/comEmail/containers/EmailCommunicationAreaContainer/EmailCommunicationAreaContainer.tsx
src/features/comEmail/views/ComEmailView.tsx → src/features/comEmail/containers/EmailCommunicatedJobSeekersContainer/index.ts
src/features/comEmail/views/ComEmailView.tsx → src/features/comEmail/containers/EmailCommunicationAreaContainer/index.ts
src/features/comEmail/views/index.ts → src/features/comEmail/views/ComEmailView.tsx
CI上で変更に影響したテストファイルを抽出してjest実行する方法
前セクションでテストファイルを抽出する方法のイメージがついたと思うので、ここからは具体的にCI上の設定を順に説明したいと思います。
PR上の変更ファイルを取得
まずはGitHub ActionsでPull Requestを出した際に、変更したファイルを取得します。これは以下のようなコードを書くことで出力できます。ポイントはチェックアウト時に過去のコミットも見れるようにfetch-depth: 0を指定しておきます。また後続の処理で扱いやすいようにファイルに出力しておきます。
jobs:
dependents-test:
if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
with:
token: ${{ secrets.CASONE_BOT_TOKEN }}
# 過去のコミット状況も見るため、0を渡して全てのコミットをfetchする
fetch-depth: 0
- name: Diff Files
run: git diff --name-only $BASE_SHA...$HEAD_SHA > diff_files.txt
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
src/features/comEmail/components/AlertDuplicateEmailJobSeekers/AlertDuplicateEmailJobSeekers.tsx
src/features/comEmail/components/JobSeekerCard/JobSeekerCard.tsx
diff_files.txtに依存しているファイルリストの出力
出力した変更ファイルリストを元に、依存しているファイルリストを出力します。ファイルリストからAlertDuplicateEmailJobSeekers.tsx|JobSeekerCard.tsxのように|で結合して渡すのは$(cat ./diff_files.txt | xargs | sed 's/ /|/g')とします。
jobs:
dependents-test:
if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
# 既に表示したものは省略
# Node.jsのセットアップとパッケージのインストール
- uses: actions/setup-node@v4
with:
node-version-file: 'package.json'
cache: 'npm'
- name: Install
run: npm ci
# diff_files.txtに依存しているファイルリストをdependents.txtに出力
- name: Generate Dependents
run: npx depcruise src --reaches $(cat ./diff_files.txt | xargs | sed 's/ /|/g') -T text > dependents.txt
src/features/comEmail/components/AlertDuplicateEmailJobSeekers/AlertDuplicateEmailJobSeekers.stories.tsx → src/features/comEmail/components/AlertDuplicateEmailJobSeekers/AlertDuplicateEmailJobSeekers.tsx
src/features/comEmail/components/AlertDuplicateEmailJobSeekers/index.ts → src/features/comEmail/components/AlertDuplicateEmailJobSeekers/AlertDuplicateEmailJobSeekers.tsx
src/features/comEmail/components/JobSeekerCard/JobSeekerCard.stories.tsx → src/features/comEmail/components/JobSeekerCard/JobSeekerCard.tsx
src/features/comEmail/components/JobSeekerCard/JobSeekerCard.test.tsx → src/features/comEmail/components/JobSeekerCard/JobSeekerCard.tsx
src/features/comEmail/components/JobSeekerCard/index.ts → src/features/comEmail/components/JobSeekerCard/JobSeekerCard.tsx
src/features/comEmail/components/JobSeekerList/JobSeekerList.stories.tsx → src/features/comEmail/components/JobSeekerList/JobSeekerList.tsx
src/features/comEmail/components/JobSeekerList/JobSeekerList.tsx → src/features/comEmail/components/JobSeekerCard/index.ts
src/features/comEmail/components/JobSeekerList/index.ts → src/features/comEmail/components/JobSeekerList/JobSeekerList.tsx
src/features/comEmail/components/index.ts → src/features/comEmail/components/AlertDuplicateEmailJobSeekers/index.ts
src/features/comEmail/containers/EmailCommunicatedJobSeekersContainer/EmailCommunicatedJobSeekersContainer.tsx → src/features/comEmail/components/JobSeekerCard/index.ts
src/features/comEmail/containers/EmailCommunicatedJobSeekersContainer/EmailCommunicatedJobSeekersContainer.tsx → src/features/comEmail/components/JobSeekerList/index.ts
src/features/comEmail/containers/EmailCommunicatedJobSeekersContainer/index.ts → src/features/comEmail/containers/EmailCommunicatedJobSeekersContainer/EmailCommunicatedJobSeekersContainer.tsx
src/features/comEmail/containers/EmailCommunicationAreaContainer/EmailCommunicationAreaContainer.tsx → src/features/comEmail/components/AlertDuplicateEmailJobSeekers/index.ts
src/features/comEmail/containers/EmailCommunicationAreaContainer/index.ts → src/features/comEmail/containers/EmailCommunicationAreaContainer/EmailCommunicationAreaContainer.tsx
src/features/comEmail/views/ComEmailView.tsx → src/features/comEmail/containers/EmailCommunicatedJobSeekersContainer/index.ts
src/features/comEmail/views/ComEmailView.tsx → src/features/comEmail/containers/EmailCommunicationAreaContainer/index.ts
src/features/comEmail/views/index.ts → src/features/comEmail/views/ComEmailView.tsx
dependents.txtからテストファイルを抽出し、jestで実行できる形に調整
依存ファイルリストからテストファイルを抽出し、それをjestで実行できる形に調整します。jestで対象ファイルを指定するにはtestMatchオプションを使うと良いので、そこに使えるような形でファイル出力します。これはスクリプトを書く必要があるので、以下のようなコードを書きました。やっていることと補足説明をざっくり書くと以下の通りです。
-
diff_files.txtとdependents.txtから.test.tsxのファイルパスを抽出してユニーク化-
diff_files.txtもチェック対象としているのは、JobSeekerCard.test.tsxのように非依存のテストファイルを編集するとそれがdependents.txtには出てこないため
-
-
**/をprefixにつけてjest-override-test-matchファイルに出力-
**/をprefixにつけて出力しているのはテスト実行時に上手くマッチしなかったため
-
import type { ParseArgsConfig } from 'node:util'
import { parseArgs } from 'node:util'
import { promises as fsPromises } from 'fs'
const options: ParseArgsConfig['options'] = {
inputDiffFileNamesFile: {
type: 'string',
default: './diff_files.txt',
},
inputDependentsFile: {
type: 'string',
default: './dependents.txt',
},
outputFile: {
type: 'string',
default: './jest-override-test-match',
},
}
const { values } = parseArgs({ options })
const inputDiffFileNamesFilePath = values.inputDiffFileNamesFile as string
const inputDependentsFilePath = values.inputDependentsFile as string
const outputJestOverrideTestMatchFilePath = values.outputFile as string
const main = async () => {
const diffsStr = await fsPromises.readFile(
inputDiffFileNamesFilePath,
'utf-8'
)
const diffTestFileNames = diffsStr
.trim()
.split('\n')
.filter((fileName) => /\.test\.tsx?$/.test(fileName))
const dependentsStr = await fsPromises.readFile(
inputDependentsFilePath,
'utf-8'
)
const dependentFiles = dependentsStr
.trim()
.split('\n')
.map((line) => line.split(' → '))
.flat()
const dependentTestFileNames = dependentFiles.filter((dependent) =>
/\.test\.tsx?$/.test(dependent)
)
const uniqueTestFiles = Array.from(
new Set([...diffTestFileNames, ...dependentTestFileNames])
)
await fsPromises.writeFile(
outputJestOverrideTestMatchFilePath,
uniqueTestFiles.map((file) => '**/' + file).join('\n'),
'utf-8'
)
}
main()
これをCI上で実行するように書くと以下のようになります。
jobs:
dependents-test:
if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
# 既に表示したものは省略
- name: Generate Jest Override Test Match
run: npx ts-node ./scripts/generateJestOverrideTestMatch.ts
**/src/features/comEmail/components/JobSeekerCard/JobSeekerCard.test.tsx
jest-override-test-matchファイルをみてテスト実行する
最後にjest-override-test-matchに出力されているテストファイルのみテスト実行されるようにjest.config.tsを編集します。全体テストでも動くように、jest-override-test-matchがある時だけそちらを参照するような書き方にしています。
import type { Config } from '@jest/types'
import fs from 'fs'
import nextJest from 'next/jest'
import path from 'path'
+const overrideTestMatch = (() => {
+ try {
+ const testMatches = fs.readFileSync(
+ path.resolve(__dirname, './jest-override-test-match'),
+ 'utf-8'
+ )
+ return testMatches.trim().split('\n')
+ } catch {
+ return undefined
+ }
+})()
const config: Config.InitialOptions = {
+ testMatch: overrideTestMatch ?? ['**/*.test.tsx?'],
// 他の設定は省略
}
const createJestConfig = nextJest({
dir: './',
})
export default createJestConfig(config)
後はそのままテストを実行したらOKです。
jobs:
dependents-test:
if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-22.04
timeout-minutes: 10
steps:
# 既に表示したものは省略
- name: Run Test
run: npm run test
全体テストも考慮したCIの調整
以上が変更に影響したテストファイルだけを抽出してテスト実行する方法ですが、jest.config.tsの設定を見て分かる通りjest-override-test-matchファイルがあるかどうかで絞り込みテストをするかを決めているので、ワークフロー自体は共通にしておいた方が保守がしやすそうなので、そのように調整します。なお、全体テストではshardを使って並列実行しているので、その辺の設定も合わせて行います。
具体的にはjest-override-test-matchファイルを出力するjobに切り出し、そこでPull Requestの時は対象ファイルを出力してupload-artifactでアップロードしておきます。そしてファイル出力していない場合はshard-numの設定だけをして、並列実行できる形にします。そして後続のtest実行用のjobでjest-override-test-matchがある場合はダウンロードしてjestを実行します。
jobs:
+ generate-jest-override-test-match:
- dependents-test:
- if: ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-22.04
timeout-minutes: 10
+ outputs:
+ shard-num: ${{ steps.set-matrix-shard.outputs.shard-num }}
steps:
- uses: actions/checkout@v3
+ if: ${{ github.event_name == 'pull_request' }}
with:
token: ${{ secrets.CASONE_BOT_TOKEN }}
# 過去のコミット状況も見るため、0を渡して全てのコミットをfetchする
fetch-depth: 0
- name: Diff Files
+ if: ${{ github.event_name == 'pull_request' }}
run: git diff --name-only $BASE_SHA...$HEAD_SHA > diff_files.txt
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
# Node.jsのセットアップとパッケージのインストール
- uses: actions/setup-node@v4
+ if: ${{ github.event_name == 'pull_request' }}
with:
node-version-file: 'package.json'
cache: 'npm'
- name: Install
+ if: ${{ github.event_name == 'pull_request' }}
run: npm ci
# diff_files.txtに依存しているファイルリストをdependents.txtに出力
- name: Generate Dependents
+ if: ${{ github.event_name == 'pull_request' }}
run: npx depcruise src --reaches $(cat ./diff_files.txt | xargs | sed 's/ /|/g') -T text > dependents.txt
- name: Generate Jest Override Test Match
+ if: ${{ github.event_name == 'pull_request' }}
run: npx ts-node ./scripts/generateJestOverrideTestMatch.ts
+ - name: Upload Jest Override Test Match
+ if: ${{ github.event_name == 'pull_request' }}
+ uses: actions/upload-artifact@v4
+ with:
+ name: jest-override-test-match
+ path: ./jest-override-test-match
+ - name: Set Matrix Shard
+ id: set-matrix-shard
+ run: |
+ if [ -f ./jest-override-test-match ]; then
+ echo "shard-num=[1]" >> "$GITHUB_OUTPUT"
+ else
+ echo 'shard-num=[1, 2, 3, 4]' >> "$GITHUB_OUTPUT"
+ fi
+ test:
+ runs-on: ubuntu-22.04
+ needs: generate-jest-override-test-match
+ timeout-minutes: 30
+ strategy:
+ matrix:
+ shard-num: ${{ fromJson(needs.generate-jest-override-test-match.outputs.shard-num) }}
+
+ steps:
+ # Node.jsのセットアップとパッケージのインストール
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: 'package.json'
+ cache: 'npm'
+ - name: Install
+ run: npm ci
+
+ # もしJestのtestMatchを上書きするファイルがあればダウンロードする
+ - name: Download Jest Override Test Match
+ uses: actions/download-artifact@v4
+ with:
+ name: jest-override-test-match
+ path: ./
+ continue-on-error: true
+
+ - name: Run Test
+ run: npm run test -- --shard=${{ matrix.shard-num }}/${{ strategy.job-total }}
その他微調整
これで大枠の設定は完了ですが、細かい微調整が残っているのでその辺について説明します。
影響のあるテストファイル数に応じてshardを動的に変更する
先ほどのCIの設定でjest-override-test-matchファイルがある場合はecho "shard-num=[1]" >> "$GITHUB_OUTPUT"と固定で一つのみとしていましたが、対象テストファイル数が多かった場合はかなり時間がかかってしまうので、テストファイル数に応じて動的に変更したいと思います。テストファイル数は単純にjest-override-test-matchの行数をカウントすれば良いのでwc -lコマンドでカウントしますが、空ファイルだとエラーになってしまうので別途条件分岐してそれぞれ設定します。後はこの値を元にshard-numの値を設定します。具体的には以下のようなコードになりました。対象テストファイル数が分かったので、0の場合はtest jobの方は実行されないようについでに設定しています。
jobs:
generate-jest-override-test-match:
runs-on: ubuntu-22.04
timeout-minutes: 10
outputs:
+ num-target-files: ${{ steps.count-target-files.outputs.num-target-files }}
shard-num: ${{ steps.set-matrix-shard.outputs.shard-num }}
steps:
# 変更がない部分は省略
+ - name: Count Target Files
+ if: ${{ github.event_name == 'pull_request' }}
+ id: count-target-files
+ run: |
+ if [ -f ./jest-override-test-match ]; then
+ if [ $(cat ./jest-override-test-match | wc -c) -eq 0 ]; then
+ echo "jest-override-test-matchファイルが空でした。"
+ echo "num-target-files=0" >> $GITHUB_OUTPUT
+ else
+ numTargetFiles=$(cat ./jest-override-test-match | wc -l)
+ # 行末に改行がないため+1する
+ numTargetFiles=$(($numTargetFiles + 1))
+ echo "num-target-files=$numTargetFiles" >> $GITHUB_OUTPUT
+ fi
+ else
+ echo "jest-override-test-matchファイルが存在しませんでした。"
+ fi
- name: Set Matrix Shard
id: set-matrix-shard
run: |
if [ -f ./jest-override-test-match ]; then
+ NUM_FILES=${{ steps.count-target-files.outputs.num-target-files }}
+ COUNT=$(( $NUM_FILES / 200 + 1 ))
+ echo "shard-num=[$(seq -s, 1 $COUNT)]" >> "$GITHUB_OUTPUT"
- echo "shard-num=[1]" >> "$GITHUB_OUTPUT"
else
echo 'shard-num=[1, 2, 3, 4]' >> "$GITHUB_OUTPUT"
fi
test:
runs-on: ubuntu-22.04
needs: generate-jest-override-test-match
+ # num-target-filesが未指定か0以外の数の場合のみ実行
+ if: ${{ needs.generate-jest-override-test-match.outputs.num-target-files == '' || needs.generate-jest-override-test-match.outputs.num-target-files != '0' }}
timeout-minutes: 30
strategy:
matrix:
shard-num: ${{ fromJson(needs.generate-jest-override-test-match.outputs.shard-num) }}
steps:
# 変更がないため省略
全体に影響があるファイルが変更された場合は全体テストに切り替える
例えばpackage-lock.jsonが変更された場合はライブラリが変わったので全体テストを行いたいと思います。そういったファイルがdiff_files.txtに含まれていた場合はPull Requestの時であってもjest-override-test-matchファイルの出力をスキップするようにして、全体テストに切り替わるように調整します。
jobs:
generate-jest-override-test-match:
runs-on: ubuntu-22.04
timeout-minutes: 10
outputs:
num-target-files: ${{ steps.generate-jest-override-test-match.outputs.num-target-files }}
shard-num: ${{ steps.set-matrix-shard.outputs.shard-num }}
steps:
- uses: actions/checkout@v3
if: ${{ github.event_name == 'pull_request' }}
with:
token: ${{ secrets.CASONE_BOT_TOKEN }}
# 過去のコミット状況も見るため、0を渡して全てのコミットをfetchする
fetch-depth: 0
- name: Diff Files
if: ${{ github.event_name == 'pull_request' }}
run: git diff --name-only $BASE_SHA...$HEAD_SHA > diff_files.txt
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
+ - name: Judge Dependents Generation Required
+ if: ${{ github.event_name == 'pull_request' }}
+ id: dependents-generation
+ run: |
+ if grep "^package-lock.json$" ./diff_files.txt > /dev/null; then
+ echo "package-lock.jsonを変更した場合は全てテストするためjest-override-test-matchの出力をスキップします。"
+ echo "skip=true" >> $GITHUB_OUTPUT
+ fi
# Node.jsのセットアップとパッケージのインストール
- uses: actions/setup-node@v4
- if: ${{ github.event_name == 'pull_request' }}
+ if: ${{ steps.dependents-generation.outputs.skip != 'true' }}
with:
node-version-file: 'package.json'
cache: 'npm'
- name: Install
- if: ${{ github.event_name == 'pull_request' }}
+ if: ${{ steps.dependents-generation.outputs.skip != 'true' }}
run: npm ci
# diff_files.txtに依存しているファイルリストをdependents.txtに出力
- name: Generate Dependents
- if: ${{ github.event_name == 'pull_request' }}
+ if: ${{ steps.dependents-generation.outputs.skip != 'true' }}
run: npx depcruise src --reaches $(cat ./diff_files.txt | xargs | sed 's/ /|/g') -T text > dependents.txt
- name: Generate Jest Override Test Match
- if: ${{ github.event_name == 'pull_request' }}
+ if: ${{ steps.dependents-generation.outputs.skip != 'true' }}
run: npx ts-node ./scripts/generateJestOverrideTestMatch.ts
- name: Upload Jest Override Test Match
- if: ${{ github.event_name == 'pull_request' }}
+ if: ${{ steps.dependents-generation.outputs.skip != 'true' }}
uses: actions/upload-artifact@v4
with:
name: jest-override-test-match
path: ./jest-override-test-match
- name: Count Target Files
- if: ${{ github.event_name == 'pull_request' }}
+ if: ${{ steps.dependents-generation.outputs.skip != 'true' }}
id: count-target-files
run: |
if [ -f ./jest-override-test-match ]; then
if [ $(cat ./jest-override-test-match | wc -c) -eq 0 ]; then
echo "jest-override-test-matchファイルが空でした。"
echo "num-target-files=0" >> $GITHUB_OUTPUT
else
numTargetFiles=$(cat ./jest-override-test-match | wc -l)
# 行末に改行がないため+1する
numTargetFiles=$(($numTargetFiles + 1))
echo "num-target-files=$numTargetFiles" >> $GITHUB_OUTPUT
fi
else
echo "jest-override-test-matchファイルが存在しませんでした。"
fi
- name: Set Matrix Shard
id: set-matrix-shard
run: |
if [ -f ./jest-override-test-match ]; then
NUM_FILES=${{ steps.count-target-files.outputs.num-target-files }}
COUNT=$(( $NUM_FILES / 200 + 1 ))
echo "shard-num=[$(seq -s, 1 $COUNT)]" >> "$GITHUB_OUTPUT"
else
echo 'shard-num=[1, 2, 3, 4]' >> "$GITHUB_OUTPUT"
fi
test:
# 変更がないためスキップ
アプリケーションディレクトリのみの絞り込み
今までの説明では全てルートディレクトリで行っていましたが、モノレポ運用の場合、変更ファイルリストなどのパスは全てフルパスではなく対象のアプリケーションディレクトリからの相対パスに調整する必要があります。例えば弊社では実際はapps/tenantなどにアプリケーションディレクトリがあるため、以下のように出力されています。
apps/tenant/src/features/comEmail/components/AlertDuplicateEmailJobSeekers/AlertDuplicateEmailJobSeekers.tsx
apps/tenant/src/features/comEmail/components/JobSeekerCard/JobSeekerCard.tsx
これを以下のようなコマンドでapp/tenant部分を取り除いて扱っていますが、こちらの設定方法を詳しく説明するとGitHub Actionsのworking-directoryの調整とかも必要になって話が長くなってしまうので割愛させていただきますが、実際はこのような調整もしています。
grep '^apps/tenant' ./diff_files.txt | sed 's|^apps/tenant||' > ./trimmed_diff_files.txt
終わりに
以上が変更に影響したテストファイルだけjest実行してCIの時間を大幅に短縮させる方法でした。今までは軽微な変更をするたびに全部テストを回していて時間がかかっており非常に億劫でしたが、これを導入したことですぐテストが終わるようになって非常にサイクルが速くなったと思います😊
この記事ではjestで説明しましたが、Vitestなど他のテストツールでもjest-override-test-matchのところだけ調整すれば動くと思いますので参考になると思います。ただbun testだと圧倒的に速くなるようなのでシュッと乗り換えられるならそちらに乗り換えてしまうという案もある気はしています。弊社はbun testへの移行にどれだけつまづくのかが不透明だったので一旦対象テストファイルを絞り込むというアプローチを取りましたが、将来的にはbun testへの乗り換えも検討しています。
軽微な変更なのに全テストするのがもどかしいと思っている方の参考になれば幸いです。
Discussion