starfish

I'm Multistack Engineer.

GitHub ActionsでのUnit Testを高速化する

2021-05-29GitHub

GitHub ActionsでのUnit Testを高速化する

GitHub ActionsでUnit Testを回している人も増えてきたと思いますが、この記事では毎回のCIを高速化する手法を紹介していきます。

実際に業務で実践したところ、

  • 15分 -> 5分
  • 20分 -> 8分

程度には改善することができました。

今回紹介する手法をまとめたサンプルリポジトリも用意したので、ぜひ参考にしてみてください。

手法の紹介

キャッシュを利用する

CIを実行するたびにnpmやgemのパッケージをインストールしていると、その分の実行時間が掛かってしまいます。

GitHub Actionsではパッケージのキャッシュを作成することが可能で、これを利用することでパッケージをインストールする時間を節約する事が可能になります。

actions/cache

例えばnpmを利用している場合は、以下のような記述でキャッシュを作成、利用する事が可能です。

- uses: actions/cache@v2
  id: cache
  with:
    path: node_modules
    key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies
  if: steps.cache.outputs.cache-hit != 'true'
  run: npm ci

CIの実行対象となるファイルを絞り込む

GitHub Actionsではworkflowを実行するタイミングを指定する事が可能です。

Unit Testを実行するタイミングとしては、プルリクエストが作成、更新されたタイミングになるかと思います。

実行タイミングに on: pull_request を指定していることをよく見かけますが、これは非常に効率が悪いと考えています。

on: pull_request

なぜならば、READMEの更新だけであればUnit Testを実行する必要がないからです。

Unit Testを実行する必要があるファイルを洗い出して、 on: pullrequest: paths を利用してファイルを指定するようにしましょう。

on:
  pull_request:
    paths:
      - '.github/workflows/test.yaml'
      - 'src/**'
      - 'package.json'
      - 'package-lock.json'
      - 'test_split_files.sh'

これをすることによって、不要なUnit Testを実行する事がなくなり、GitHub Actionsの実行時間を節約する事が可能になります。

workflowを分割実行できるようにする

最後に紹介する手法は、workflowを分割実行する手法です。

GitHub Actionsには strategy: matrix という機能があり、これを利用することによってUnit Testの分割実行を実現させます。

strategy:
  fail-fast: false
  matrix:
    ci_node_total: [3]
    ci_node_index: [0, 1, 2]

今回はmatrixの部分に分割数を指定します。

そして実際にUnit Testを実行する部分で利用します。

- name: Run tests
  env:
    CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
    CI_NODE_INDEX: ${{ matrix.ci_node_index }}
  run: |
    chmod +rx ./test_split_files.sh
    TEST_FILES="$(find ./src/__tests__ -type f -name "*.test.js" | xargs ./test_split_files.sh)"
    npm run test $TEST_FILES

ここは一つずつ解説していきます。

env の部分でmatrixに指定した分割数に関する情報を環境変数に設定します。

次に chmod でテスト対象のファイルを分割するシェルに実行権限を与えます。

実行権限を与えなかった場合、findやシェルの中でアクセス権限を持っていないテストファイルがあった場合、そのテストが実行対象から外れてしまう可能性が出てきます。

findで全テストファイルのリストを作成してシェルに渡し、シェルの中で分割数の情報に合致するテストファイルのリストを作成します。

例えばテストファイルが3つあり、分割数が3の場合、テストファイルを1つだけ実行するworkflowが3本並列でテストが実行されることになります。

最後に、分割数に合致したテストファイルの一覧をtestコマンドに渡し、workflowを分割してのUnit Testの実行を実現しています。

シェルの中身に関しては、サンプルリポジトリを参照してください。

この手法は、プログラム言語やフレームワークに依存することなく利用することが可能なので、非常に汎用性の高い手法だと思います。