Skip to main content

コンカレンシー、式、テスト マトリックスの使用

継続的インテグレーション (CI) のために高度な GitHub Actions 機能を使用する方法。

注: GitHub ホステッド ランナーは、現在 GitHub Enterprise Server でサポートされていません。 GitHub public roadmap で、今後の計画的なサポートの詳細を確認できます。

サンプルの概要

この記事では、ワークフローの例を使って、GitHub Actions の主な CI 機能の一部を示します。このワークフローがトリガーされると、npm test とテストの組み合わせのマトリックスを使ってコードがテストされます。

次の図は、ワークフローの手順とジョブ内でそれを実行する方法の概要を示したものです。

ワークフローのステップの概要図

この例で使用されている機能

このワークフロー例は、GitHub Actions の次の機能を示しています。

機能実装
UI からワークフローを手動で実行する:workflow_dispatch

ワークフローの例

次のワークフローは、GitHub ドキュメント エンジニアリング チームによって作成されました。 github/docs リポジトリ内でこのファイルの最新バージョンを確認するには、次を参照してください。 test.yml

: このワークフローの各行については、次のセクションの「例の説明」に説明があります。

YAML
name: Node.js Tests

# **What it does**: Runs our tests.
# **Why we have it**: We want our tests to pass before merging code.
# **Who does it impact**: Docs engineering, open-source engineering contributors.

on:
  workflow_dispatch:
  pull_request:
  push:
    branches:
      - main

permissions:
  contents: read
  # Needed for the 'trilom/file-changes-action' action
  pull-requests: read

# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
  group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
  cancel-in-progress: true

jobs:
  test:
    # Run on self-hosted if the private repo or ubuntu-latest if the public repo
    # See pull # 17442 in the private repo for context
    runs-on: ${{ fromJSON('["ubuntu-latest", "self-hosted"]')[github.repository == 'github/docs-internal'] }}
    timeout-minutes: 60
    strategy:
      fail-fast: false
      matrix:
        # The same array lives in test-windows.yml, so make any updates there too.
        test-group:
          [
            content,
            graphql,
            meta,
            rendering,
            routing,
            unit,
            linting,
            translations,
          ]
    steps:
      # Each of these ifs needs to be repeated at each step to make sure the required check still runs
      # Even if if doesn't do anything
      - name: Check out repo
        uses: actions/checkout@v3
        with:
          # Not all test suites need the LFS files. So instead, we opt to
          # NOT clone them initially and instead, include them manually
          # only for the test groups that we know need the files.
          lfs: ${{ matrix.test-group == 'content' }}
          # Enables cloning the Early Access repo later with the relevant PAT
          persist-credentials: 'false'

      - name: Figure out which docs-early-access branch to checkout, if internal repo
        if: ${{ github.repository == 'github/docs-internal' }}
        id: check-early-access
        uses: actions/github-script@v6
        env:
          BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
        with:
          github-token: ${{ secrets.DOCUBOT_REPO_PAT }}
          result-encoding: string
          script: |
            // If being run from a PR, this becomes 'my-cool-branch'.
            // If run on main, with the `workflow_dispatch` action for
            // example, the value becomes 'main'.
            const { BRANCH_NAME } = process.env
            try {
              const response = await github.repos.getBranch({
                owner: 'github',
                repo: 'docs-early-access',
                BRANCH_NAME,
              })
              console.log(`Using docs-early-access branch called '${BRANCH_NAME}'.`)
              return BRANCH_NAME
            } catch (err) {
              if (err.status === 404) {
                console.log(`There is no docs-early-access branch called '${BRANCH_NAME}' so checking out 'main' instead.`)
                return 'main'
              }
              throw err
            }

      - name: Check out docs-early-access too, if internal repo
        if: ${{ github.repository == 'github/docs-internal' }}
        uses: actions/checkout@v3
        with:
          repository: github/docs-early-access
          token: ${{ secrets.DOCUBOT_REPO_PAT }}
          path: docs-early-access
          ref: ${{ steps.check-early-access.outputs.result }}

      - name: Merge docs-early-access repo's folders
        if: ${{ github.repository == 'github/docs-internal' }}
        run: |
          mv docs-early-access/assets assets/images/early-access
          mv docs-early-access/content content/early-access
          mv docs-early-access/data data/early-access
          rm -r docs-early-access

      # This is necessary when LFS files where cloned but does nothing
      # if actions/checkout was run with `lfs:false`.
      - name: Checkout LFS objects
        run: git lfs checkout

      - name: Gather files changed
        uses: trilom/file-changes-action@a6ca26c14274c33b15e6499323aac178af06ad4b
        id: get_diff_files
        with:
          # So that `steps.get_diff_files.outputs.files` becomes
          # a string like `foo.js path/bar.md`
          output: ' '

      - name: Insight into changed files
        run: |

          # Must to do this because the list of files can be HUGE. Especially
          # in a repo-sync when there are lots of translation files involved.
          echo "${{ steps.get_diff_files.outputs.files }}" > get_diff_files.txt

      - name: Setup node
        uses: actions/setup-node@v3
        with:
          node-version: 16.14.x
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Cache nextjs build
        uses: actions/cache@v3
        with:
          path: .next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('package*.json') }}

      - name: Run build script
        run: npm run build

      - name: Run tests
        env:
          DIFF_FILE: get_diff_files.txt
          CHANGELOG_CACHE_FILE_PATH: tests/fixtures/changelog-feed.json
        run: npm test -- tests/${{ matrix.test-group }}/

例の説明

次の表では、GitHub Actions ワークフローを作成するときにこれらの各機能がどのように使われるかを説明します。

"コード" 説明
YAML
name: Node.js Tests

GitHub リポジトリの [アクション] タブに表示されるワークフローの名前。

YAML
on:

on キーワードを使うと、ワークフローの実行時にトリガーするイベントを定義できます。 ここでは複数のイベントを定義できます。 詳細については、「ワークフローのトリガー」を参照してください。

YAML
  workflow_dispatch:

UI でこのワークフローを手動で実行できるようにする場合は、workflow_dispatch イベントを追加します。 詳細については、「workflow_dispatch」を参照してください。

YAML
  pull_request:

pull request が作成または更新されるたびにワークフローが自動的に実行されるようにするには、pull_request イベントを追加します。 詳細については、「pull_request」を参照してください。

YAML
  push:
    branches:
      - main

フィルター main に一致するブランチにコミットがプッシュされるたびに、ワークフローが自動的に実行されるようにするには、push イベントを追加します。 詳細については、「push」を参照してください。

YAML
permissions:
  contents: read
  pull-requests: read

GITHUB_TOKEN に付与される既定のアクセス許可を変更します。 これはワークフローのニーズによって異なります。 詳細については、「ジョブへのアクセス許可の割り当て」を参照してください。

YAML
concurrency:
  group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'

特定のイベントに対するコンカレンシー グループを作成し、|| 演算子を使ってフォールバック値を定義します。 詳細については、「コンカレンシーの使用」を参照してください。

YAML
  cancel-in-progress: true

同じコンカレンシー グループ内の現在実行中のジョブまたはワークフローを取り消します。

YAML
jobs:

ワークフロー ファイルで実行されるすべてのジョブをグループ化します。

YAML
  test:

jobs キー内に格納されている ID test を持つジョブを定義します。

YAML
    runs-on: ${{ fromJSON('["ubuntu-latest", "self-hosted"]')[github.repository == 'github/docs-internal'] }}

ワークフローを実行するリポジトリに応じて、GitHub ホステッド ランナーまたはセルフホステッド ランナー上で実行するようにジョブを構成します。 この例では、リポジトリ名が docs-internal であり、github 組織内にある場合、ジョブはセルフホステッド ランナー上で実行されます。 このパスに一致しないリポジトリは、GitHub によってホストされる ubuntu-latest ランナー上で実行されます。 これらのオプションの詳細については、「ジョブのランナーを選択する」を参照してください。

YAML
    timeout-minutes: 60

実行したジョブが自動的に取り消されるまでの最大分数を設定します。 詳細については、「timeout-minutes」を参照してください。

YAML
    strategy:
このセクションで、ジョブのビルド マトリックスを定義します。
YAML
      fail-fast: false

fail-fastfalse に設定すると、マトリックス ジョブのいずれかが失敗した場合に、進行中のすべてのジョブが GitHub によって取り消されなくなります。

YAML
      matrix:
        test-group:
          [
            content,
            graphql,
            meta,
            rendering,
            routing,
            unit,
            linting,
            translations,
          ]

テスト グループの配列を含む test-group というマトリックスを作成します。 これらの値は npm test によって実行されるテスト グループの名前と一致します。

YAML
    steps:

test ジョブの一部として実行されるすべてのステップをグループ化します。 ワークフロー内の各ジョブには、独自の steps セクションがあります。

YAML
      - name: Check out repo
        uses: actions/checkout@v3
        with:
          lfs: ${{ matrix.test-group == 'content' }}
          persist-credentials: 'false'

uses キーワードは、actions/checkout という名前のアクションを取得するようにジョブに指示します。 これは、リポジトリをチェックアウトしてランナーにダウンロードし、コードに対してアクション(テストツールなど)を実行できるようにします。 ワークフローがリポジトリのコードに対して実行されるとき、またはリポジトリで定義されたアクションを使用しているときはいつでも、チェックアウトアクションを使用する必要があります。 このアクションには、with キーを使ういくつかの追加オプションが用意されています。

YAML
      - name: Figure out which docs-early-access branch to checkout, if internal repo
        if: ${{ github.repository == 'github/docs-internal' }}
        id: check-early-access
        uses: actions/github-script@v6
        env:
          BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
        with:
          github-token: ${{ secrets.DOCUBOT_REPO_PAT }}
          result-encoding: string
          script: |
            // If being run from a PR, this becomes 'my-cool-branch'.
            // If run on main, with the `workflow_dispatch` action for
            // example, the value becomes 'main'.
            const { BRANCH_NAME } = process.env
            try {
              const response = await github.repos.getBranch({
                owner: 'github',
                repo: 'docs-early-access',
                BRANCH_NAME,
              })
              console.log(`Using docs-early-access branch called '${BRANCH_NAME}'.`)
              return BRANCH_NAME
            } catch (err) {
              if (err.status === 404) {
                console.log(`There is no docs-early-access branch called '${BRANCH_NAME}' so checking out 'main' instead.`)
                return 'main'
              }
              throw err
            }

現在のリポジトリが github/docs-internal リポジトリである場合、この手順では actions/github-script アクションを使ってスクリプトを実行し、docs-early-access というブランチがあるかどうかを確認します。

YAML
      - name: Check out docs-early-access too, if internal repo
        if: ${{ github.repository == 'github/docs-internal' }}
        uses: actions/checkout@v3
        with:
          repository: github/docs-early-access
          token: ${{ secrets.DOCUBOT_REPO_PAT }}
          path: docs-early-access
          ref: ${{ steps.check-early-access.outputs.result }}

現在のリポジトリが github/docs-internal リポジトリである場合、この手順では前の手順で特定された github/docs-early-access のブランチをチェックアウトします。

YAML
      - name: Merge docs-early-access repo's folders
        if: ${{ github.repository == 'github/docs-internal' }}
        run: |
          mv docs-early-access/assets assets/images/early-access
          mv docs-early-access/content content/early-access
          mv docs-early-access/data data/early-access
          rm -r docs-early-access

現在のリポジトリが github/docs-internal リポジトリである場合、この手順では run キーワードを使ってシェル コマンドを実行し、docs-early-access リポジトリのフォルダーをメイン リポジトリのフォルダーに移動します。

YAML
      - name: Checkout LFS objects
        run: git lfs checkout

この手順では、リポジトリから LFS オブジェクトをチェックアウトするコマンドを実行します。

YAML
      - name: Gather files changed
        uses: trilom/file-changes-action@a6ca26c14274c33b15e6499323aac178af06ad4b
        id: get_diff_files
        with:
          # So that `steps.get_diff_files.outputs.files` becomes
          # a string like `foo.js path/bar.md`
          output: ' '

この手順では、trilom/file-changes-action アクションを使って、pull request で変更されたファイルを収集し、次の手順で分析できるようにします。 この例は、a6ca26c14274c33b15e6499323aac178af06ad4b SHA を使用して、特定のバージョンのアクションに合わせて固定されています。

YAML
      - name: Insight into changed files
        run: |
          echo "${{ steps.get_diff_files.outputs.files }}" > get_diff_files.txt

この手順では、前の手順の出力を使って、pull request で変更されたファイルの一覧を含むファイルを作成するシェル コマンドを実行します。

YAML
      - name: Setup node
        uses: actions/setup-node@v3
        with:
          node-version: 16.14.x
          cache: npm

この手順では、actions/setup-node アクションを使用して、指定したバージョンの node ソフトウェア パッケージをランナーにインストールします。これにより、npm コマンドにアクセスできるようになります。

YAML
      - name: Install dependencies
        run: npm ci

この手順では、npm ci シェル コマンドを実行して、プロジェクトの npm ソフトウェア パッケージをインストールします。

YAML
      - name: Cache nextjs build
        uses: actions/cache@v3
        with:
          path: .next/cache
          key: ${{ runner.os }}-nextjs-${{ hashFiles('package*.json') }}

この手順では、ワークフローでビルドのキャッシュを取得し、毎回ゼロからリビルドしなくて済むように、actions/cache アクションを使って Next.js ビルドをキャッシュします。 詳細については、「依存関係をキャッシュしてワークフローのスピードを上げる」を参照してください。

YAML
      - name: Run build script
        run: npm run build

この手順では、ビルド スクリプトを実行します。

YAML
      - name: Run tests
        env:
          DIFF_FILE: get_diff_files.txt
          CHANGELOG_CACHE_FILE_PATH: tests/fixtures/changelog-feed.json
        run: npm test -- tests/${{ matrix.test-group }}/

この手順では、npm test を使ってテストを実行します。テスト マトリックスには、マトリックス内の各ジョブに対する ${{ matrix.test-group }} のさまざまな値を指定されています。 これには、変更されたファイルがわかる DIFF_FILE 環境変数が使用され、変更ログ キャッシュ ファイル用の CHANGELOG_CACHE_FILE_PATH 環境変数が使用されています。

次の手順