LoginSignup
4
3

Jestで実行時間を短縮する方法を調査した

Last updated at Posted at 2023-09-26

目次

  1. 背景
  2. jest,ts-jestとは
  3. testのタイミング
  4. 検証環境
  5. 検証内容
    5.1. runInBand
    5.2. max-workers
    5.3. isolatedModules
    5.4. lastCommit,onlyChanged
    5.5. CIでJest cacheの保存・リストア
    5.6. CI自体の並列実行
    5.7. jest-light-runner
    5.8. swc/jest
    5.10. esbuild-jest
    5.11. jest configファイルをjs、jsonにする
  6. 検証結果
    6.1. runInBand
    6.2. max-workers
    6.3. CIでJest cacheの保存・リストア
    6.4. tsc + swc/jest
    6.5. jest configファイルをjs、jsonにする
  7. その他の改善案
  8. ts-jestが今後高速化する?
  9. 最後に

1. 背景

弊社ではフロントエンドのテストでjest,ts-jestを採用しています。
テストコード、ファイルの量が増えるに連れてテストの実行時間が膨大になるのは自明です。
そこで、少しでも実行時間を短縮したいなと思ったからです。

2.jest,ts-jest

jest

  • JavaScriptのテストフレームワークです。
  • テストランナー、アサーション、テストモック・テストダブルがオールインワンになっています。

3.testのタイミング

  • リモートにpush時
  • ローカルで好きなタイミングで実行

ts-jest

  • TypeScriptで書いたテストコードを、実行前にJavaScritpにトランスパイルします。
  • 型チェックを含むTypeScriptのすべての機能をサポートしています。

4. 検証環境

こちらのリポジトリで行います。

5.検証方法

5.1. runInBand

runInBandとは

1つのプロセスですべてのテストをシリアル(順番)に実行します。

Jestは、高速なSSDを搭載した最新のマルチコアコンピュータ上ではほとんどの場合非常に高速ですが、特定のセットアップ上では遅くなることがあります。
速度を最大50%改善する1つの方法は、テストを連続して実行することです。
これを行うには、--runInBand を使って同じスレッドでテストを実行します

# Using Jest CLI
jest --runInBand

# Using your package manager's `test` script (e.g. with create-react-app)
npm test -- --runInBand

5.2.max-workers

max-workersとは

  • テストの実行時にワーカープールが生成するワーカの最大数を指定します。
  • シングルランモードでは、マシンで使用可能なコア数からメインスレッド用に1を引いた数がデフォルトです。
  • CIのようなリソースが限られた環境ではこれを調整するのが便利かもしれませんが、ほとんどのユースケースではデフォルトで十分だそうです。
  • 利用可能なCPUが変化する環境では、パーセンテージベースの設定もできます。

ローカル環境下では--maxWorkers=49%という記事を発見

github-actionsでの使用例

github-actions-cpu-coresでCPUの数を検出し、それをJestに渡します。

- name: Get number of CPU cores
  id: cpu-cores
  uses: SimenB/github-actions-cpu-cores@v2
- name: run tests
  run: npm jest --max-workers ${{ steps.cpu-cores.outputs.count }}

5.3 isolatedModules

ts-jestが使用するtsconfigのincludeの値を変更することで、このモードを使用する際のパフォーマンスを向上させる方法があります。
includeで提供されるファイルが少なければ少ないほど、テスト実行のパフォーマンスが向上します。

複数のファイルをimportしてテストするのが多いのでこちらは却下とします。

5.4 lastCommit,onlyChanged

ミツモア様のブログに以下の記述がされていましたので却下とします。

修正数に応じてテスト時間にばらつきがでたり、 多くのファイルに依存しているファイルを編集するとほとんどのファイルが実行されたり、 そしてごく稀に直接依存しないテストで不具合が見つかったりしたこともあった(例: node の version up でうまく動かなくなったファイルなど)ため、 現在ではこのオプションは利用せず全実行させるようにしています

5.5. CIでJest cacheの保存・リストア

以下2つの記事を参考に実装します。

CI で cache しやすいようにcacheの配置先を cacheDirectory で明示的に指定します。
cacheDirectory とは Jest がキャッシュされた依存情報を保存するディレクトリです。

jest.config.ts
  // ...
  cacheDirectory: "node_modules/.cache/jest"
  // ...
}
name: Test CI

on:
  push:
    branches:
      - '*'
  pull_request:
    branches:
      - '*'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

    - name: Use Node.js
      uses: actions/setup-node@v3
      with:
        node-version: 18

    - name: Install dependencies
      run: npm ci

    - name: cache jest
      uses: actions/cache@v3
      with:
        path: node_modules/.cache/jest
        key: jest-v1-${hashFiles('package-lock.json')}-${{ github.ref_name }}-${{ github.sha }}
        restore-keys: |
            jest-v1-

    - name: Get number of CPU cores
      id: cpu-cores
      uses: SimenB/github-actions-cpu-cores@v2

    - name: Run Jest tests
      run: npx jest  --max-workers ${{ steps.cpu-cores.outputs.count }}

5.6 CI自体の並列実行

以下の記事にも書いてある通り、job自体の実行時間の短縮はされないので却下とします
github actions 上のテストを並列化する

github actions の実行時間無料枠の消費は job 単位(runs-on単位)となるため、パイプラインとしての実行時間は短縮されるが無料枠の消費時間は変わらない。

5.7 jest-light-runner

環境を仮想化することなく、素のNode.jsで直接テストを実行するJestランナーです。

使い方は以下のようにpackage.jsonか、jest.configを修正してください。

package.json

{
  "jest": {
    "runner": "jest-light-runner"
  }
}
jest.config.js
module.exports = {
  runner: "jest-light-runner",
};

記事で以下のように書かれてました。

全てのjest機能がこのランナーでサポートされているわけではないです。
最もよく使われる機能は全て動作します。

実際に何が使えて何が使えないのかはjest-light-runnerの公式に書いてました。
他に欠けているJest機能があるかもしれないとのことです。
image.png

以下の理由で不採用とします。

  • jestのゼロコンフィグの良さが失われる
  • コミュニティが小さい中で設定をカスタマイズして運用していく(テストを独立して実行させたり、mockのimport等)のが難しそう

興味はあるので実際どのくらい速くなるのか今度検証してみます🙏。

5.8. swc/jest

npm i --save-dev @swc/jest
jest.config.ts
transform: {
    '^.+\\.ts$': ['@swc/jest'],
  }

以下の問題点があります。

  • 型チェックがされない
  • spyonが使えない

spyonを使えるようにする設定につきましては別記事で投稿します🙏。

型チェックをするように設定します。
tsconfig.jsonの主要オプションを理解する
以下です。

package.json
"scripts": {
   
    "tsc-swc-jest": "tsc --noEmit && npx jest"
  }

tscで型チェック、トランスパイルはswcで行うという流れです。

processNumbers3.test.ts
import { processNumbers } from "../src/processNumbers";

describe("processNumbers", () => {
  it("should double each number in the array", () => {
    const numbers = [1, 2, 3];
    const result = processNumbers(numbers);
    expect(result).toEqual([2, 4, 6]);
  });

  it("should return an empty array if no numbers are provided", () => {
    const numbers: number[] = [];
    const result = processNumbers('');
    //tscの型チェックでここがエラーになります。
    expect(result).toEqual([]);
  });
});
~/Desktop/github.com/yamatai12/jest-lab(test/swc-jest) $ npm run tsc-swc-jest

> jest-lab@1.0.0 tsc-swc-jest
> tsc --noEmit && npx jest

tests/processNumbers3.test.ts:12:35 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'number[]'.

12     const result = processNumbers('');
                                     ~~
Found 1 error in tests/processNumbers3.test.ts:12

確かにエラーになりました。

この設定で検証します。

5.9. jest configファイルをjs、jsonにする

JestでTypeScriptを高速化する
こちらの記事でconfigファイルをtsからjs,json形式にした方が速いと記載されていました。

トランスパイルする時間がconfigファイル分のみ短縮されるのであまり大きな効果は見込めないとは思いますが、検証します。

5.10. esbuild-jest

最近のバージョンアップ履歴が3年前なのも考慮して、esbuild-jestは不採用とします。

6.検証結果

6.1. runInBand

ローカル

1回目 2回目 3回目
runInBand 30.187s 30.028s 30.293s
defalut 7.919s 7.562s 7.535s
平均
runInBand 30.169s
defalut 7.672s

むしろ実行時間が増えたので runInBand は不採用 とします。

6.2. max-workers

ローカル --maxWorkers=49%

1回目 2回目 3回目
maxWorkers=49% 8.201s 8.382s 8.308s
defalut 7.919s 7.562s 7.535s
平均
maxWorkers=49% 8.297s
defalut 7.672s

むしろ実行時間が増えたので--maxWorkers=49% は不採用 とします。

github-actions

1回目 2回目 3回目
maxWorkers=github-actions-cpu-cores 2m17s 2m14s 2m26s
defalut 2m35s 3m46s 3m18s
平均
maxWorkers=github-actions-cpu-cores 139s
defalut 193s

jest公式documentの設定にすると、
github-actionsでの実行時間が短縮されることを確認できました。

6.3. CI で Jest cache の保存・リストア

github-actions

ci全体の実行時間です。

1回目 2回目 3回目
cache+maxWorkers=github-actions-cpu-cores 2m2s 1m54s 2m25s
maxWorkers=github-actions-cpu-cores 2m29s 2m27s 2m42s
平均
cache+maxWorkers=github-actions-cpu-cores 127s
maxWorkers=github-actions-cpu-cores 153s

cacheを使うことで短縮されることを確認できました。

6.4. tsc + swc/jest

ローカル

1回目 2回目 3回目
swc/jest 5.86s 5.742s 5.591s
defalut 7.919s 7.562s 7.535s
平均
swc/jest 4.3602s
defalut 7.672s

短縮されました。

github-actions

1回目 2回目 3回目
maxWorkers=github-actions-cpu-cores + tsc + swc/jest 1m59s 1m29s 1m19s
maxWorkers=github-actions-cpu-cores 2m17s 2m14s 2m26s
平均
maxWorkers=github-actions-cpu-cores + swc/jest 96s
maxWorkers=github-actions-cpu-cores 139s

短縮されました。

6.5. jest configファイルをjs、jsonにする

ローカル

1回目 2回目 3回目
js 7.404s 7.383s 7.439s
defalut 7.919s 7.562s 7.535s
平均
js 5.6382s
defalut 7.672s

github-actions

1回目 2回目 3回目
maxWorkers=github-actions-cpu-cores+config-js 2m21s 2m23s 1m40s
maxWorkers=github-actions-cpu-cores 2m17s 2m14s 2m26s
平均
maxWorkers=github-actions-cpu-cores+config-js 128s
maxWorkers=github-actions-cpu-cores 139s
  • 大した差はなかったです。
  • configファイルをts化する方がメリットが大きい(型チェックによる設定誤りの防止)ので不採用とします。

7. その他の改善案

  • ファイル単位で遅いテストを改善する
  • 全テストケースで共通的に実行される処理の見直し
  • テスト戦略の見直し
    • 例)テストはとにかく速さ重視

8. ts-jestが今後高速化する?

そのような記事を見つけました。
ts-jest が esbuild/swc をトランスフォーマーに使って高速化していた

image.png
Presets

Support for esbuild or swc
以下のようなissueが上がっていて、プルリクは作成されてないのでまだ着手されてないようです。
image.png
CHANGELOGにもそのような文言は見当たりません。

9. 最後に

local

  • defaultの設定のまま

github-actions

swc/jestをtransformerに設定するのは以下を満たせば良いかなと思いました。

  • テスト戦略的に速さを重視するのか
  • swc/jestにすることでjestで使えない機能が出てしまうことによる工数の増加、テスト実装の柔軟性が失われるのか
  • 本番環境と異なるトランスパイラを使用することによるテスト漏れ、バグの検証で時間がかかる可能性が低いのか
  • トランスパイラを複数使うことによる運用工数があまりかからないのか

弊社は本番でvite(esbuild)、テストでts-jest(tsc)を採用しています。
swc/jest、ts-jestによるテストコードの書き換えが特にないのであれば、テストでswc/jestを採用しても問題なさそうです。
また、使用する際は tsc --noEmitを実行してからswcでトランスパイルする方法が型チェックもできて安全かなと思います。

ts-jestが高速化したら、ts-jestに戻すのもありだなと思いました。

vitestに乗り換えるメリットが大きいかは次回調査します。

参考になる記事を書いてくださった皆様に感謝致します!

参考

4
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3