1261文字
6分
編集

VitestのカバレッジレポートをGithub Actionsで添付する実装例

davelosert/vitest-coverage-report-actionを利用することで簡単にVitestのカバレッジレポートを添付することが出来る。

#動作確認環境

次の環境で動作を確認している。

  • Vitest v4.0.16
  • vitest-coverage-report-action v2.9.0

#最小実装

ただカバレッジレポートを添付するだけの場合は、設定ファイルにreporterを指定し、カバレッジレポートを取得するステップの後ろでdavelosert/vitest-coverage-report-actionを呼び出すだけで良い。

yml
# .github/workflows/test.yml
# ...
jobs:
    steps:
        # ...
    - run: npx vitest run --coverage
    - uses: davelosert/vitest-coverage-report-action@v2
ts
// vitest.config.ts
// ...
export default defineConfig({
  // ...
  test: {
    // ...
    coverage: {
      reporter: ["text", "json-summary", "json"],
    },
  },
});

#テストが失敗した場合も添付する

いずれかのテストが失敗した場合でも、その他のカバレッジを確認したい場合は次のようにする。

GitHub Actionsでは通常ステップがすると後続のステップが実行されないため、if: always()を利用してカバレッジレポートを添付するステップを常に実行されるようにする。

yml
# .github/workflows/test.yml
# ...
jobs:
    steps:
        # ...
    - run: npx vitest run --coverage
    - uses: davelosert/vitest-coverage-report-action@v2
      if: always()

またVitestではreportOnFailuretrueにすることで、テストが失敗した場合でもカバレッジレポートが生成されるようにする。

ts
// vitest.config.ts
// ...
export default defineConfig({
  // ...
  test: {
    // ...
    coverage: {
      reporter: ["text", "json-summary", "json"],
      reportOnFailure: true,
    },
  },
});

#しきい値未満である場合でもエラーにしない

テストを後から導入するケースなど、しきい値を目標値として利用し、エラーにしたくない場合がある。

次のようにcoverage.thresholdsの代わりにdavelosert/vitest-coverage-report-actionthreshold-iconsを利用することで、エラーにせずに目標値として分かりやすく表示することが出来る。

yml
# .github/workflows/test.yml
# ...
jobs:
    steps:
        # ...
    - run: npx vitest run --coverage
    - uses: davelosert/vitest-coverage-report-action@v2
      threshold-icons: "{0: '🔴', 40: '🟠', 70: '🟢'}"

エラーにして問題がない場合は、davelosert/vitest-coverage-report-actioncoverage.thresholdsを自動的に参照するためthreshold-iconsを指定する必要はない。

#テストがないコードも含めてカバレッジを計測する

Vitest v4ではテストが書かれていないコードをカバレッジに含めない挙動がデフォルトなため、coverage.includeでカバレッジレポートの対象にするファイルを指定する必要がある。

ts
// vitest.config.ts
import { defineConfig } from "vite";

export default defineConfig({
  // ...
  test: {
    // ...
    coverage: {
    include: ["src/**"],
      reporter: ["text", "json-summary", "json"],
      reportOnFailure: true,
    },
  },
});

逆に含めたくないファイルはcoverage.excludeで指定する。

#デフォルトブランチとの比較を行う

カバレッジレポートと比較を行う場合、json-summary-compare-pathに比較対象のカバレッジレポートを指定することで行えるが、何らかの方法で比較対象のカバレッジレポートを取得する必要がある。

公式などでは、デフォルトブランチでのテストも毎回実行することで比較対象を取得する方法が紹介されているがコストが高い。ここでは、デフォルトブランチで実行された結果をダウンロードして利用する方法を紹介する。

yml
# .github/workflows/test.yml
on:
  push:
    branches:
      - main
  pull_request:

jobs:
    steps:
        # ...
    - run: npx vitest run --coverage
    - uses: actions/upload-artifact@v6
      if: github.ref == 'refs/heads/main'
      with:
        name: coverage-main
        path: coverage/
        retention-days: 7
    - name: Get main run ID
      if: github.ref != 'refs/heads/main'
      continue-on-error: true
      run: |
        RUN_ID=$(gh run list --workflow "${{ github.workflow }}" --branch ${{ github.event.repository.default_branch }} --status success --limit 1 --json databaseId --jq '.[0].databaseId')
        echo "RUN_ID=$RUN_ID" >> $GITHUB_ENV           

    - name: Download artifact
      if: env.IS_DEFAULT_BRANCH == 'false' && env.RUN_ID != ''
      uses: actions/download-artifact@v7
      continue-on-error: true
      with:
        name: coverage-main
        run-id: ${{ env.RUN_ID }}
        github-token: ${{ github.token }}
        path: coverage-main

    - name: Ensure coverage files exist
      if: github.ref != 'refs/heads/main'
      # デフォルトブランチのカバレッジレポートが取得できない場合、
      # 現在のブランチにあるカバレッジレポートで代替する。
      run: |
        if [ ! -f coverage-main/coverage-summary.json ]; then
          echo "Main branch coverage not found, using current coverage for comparison."
          mkdir -p coverage-main
          cp coverage/coverage-summary.json coverage-main/coverage-summary.json
        fi

    - name: Report Coverage
      if: github.ref != 'refs/heads/main'
      uses: davelosert/vitest-coverage-report-action@v2
      with:
        json-summary-compare-path: coverage-main/coverage-summary.json

#pushトリガーでも添付する

davelosert/vitest-coverage-report-actionpr-numberを指定しない場合、pull_requestトリガーでのみ添付を行う。

特にpr-number: autoのように指定すると、pushトリガーでも自動的にPull Requestが特定され添付される。例えば、次のようにするとpushpull_requestトリガーどちらでも添付されるようになる。

yml
# .github/workflows/test.yml
# ...
jobs:
    steps:
        # ...
    - uses: davelosert/vitest-coverage-report-action@v2
      with:
        pr-number: ${{ github.event_name == 'pull_request' && github.event.number || 'auto' }}

#全てを組み合わせたComposite Action

davelosert/vitest-coverage-report-actionは便利だが比較を行いたい場合など、 かなり冗長な記述になる。そこで次のようにComposite Actionに切り出すことで扱いやすくすることが出来る。

yml
# action.yml
name: vitest coverage report
description: Generate vitest coverage report.

runs:
  using: composite
  steps:
    - name: Detect default branch
      shell: bash
      run: |
        echo "IS_DEFAULT_BRANCH=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}" >> $GITHUB_ENV

    - name: Upload coverage to artifacts
      if: env.IS_DEFAULT_BRANCH == 'true'
      uses: actions/upload-artifact@v6
      with:
        name: coverage-main
        path: coverage/
        retention-days: 7

    - name: Download coverage from main
      shell: bash
      if: env.IS_DEFAULT_BRANCH == 'false'
      env:
        GH_TOKEN: ${{ github.token }}
      run: |
        RUN_ID=$(gh run list --workflow "${{ github.workflow }}" --branch ${{ github.event.repository.default_branch }} --status success --limit 1 --json databaseId --jq '.[0].databaseId')
        echo "RUN_ID=$RUN_ID" >> $GITHUB_ENV
      continue-on-error: true

    - name: Download artifact
      if: env.IS_DEFAULT_BRANCH == 'false' && env.RUN_ID != ''
      uses: actions/download-artifact@v7
      with:
        name: coverage-main
        run-id: ${{ env.RUN_ID }}
        github-token: ${{ github.token }}
        path: coverage-main
      continue-on-error: true

    - name: Ensure coverage files exist
      if: env.IS_DEFAULT_BRANCH == 'false'
      shell: bash
      continue-on-error: true
      run: |
        if [ ! -f coverage-main/coverage-summary.json ]; then
          echo "Main branch coverage not found, using current coverage for comparison."
          mkdir -p coverage-main
          cp coverage/coverage-summary.json coverage-main/coverage-summary.json
        fi
    - name: Report Coverage
      if: env.IS_DEFAULT_BRANCH == 'false'
      uses: davelosert/vitest-coverage-report-action@v2
      with:
        json-summary-compare-path: coverage-main/coverage-summary.json
        pr-number: ${{ github.event_name == 'pull_request' && github.event.number || 'auto' }}
        threshold-icons: "{0: '🔴', 20: '🟠', 60: '🟢'}"
編集