📈

Codecov非対応の言語でもCodecovみたいなことをしたい時

2022/11/29に公開

Codecov は、PR へのコメントや README のバッジのような方法でコードのカバレッジを可視化できるツールです。
カバレッジを開発者に対して頻繁にフィードバックすることで、開発者はテストを意識するようになります。

一方で世の中には星の数ほど言語がありますが Codecov がサポートしているものは意外と少ないです。
https://docs.codecov.com/docs/supported-languages

また色々な理由で Codecov を使いたくない / 使えないという場合もあるかと思います。

この記事では Codecov 非対応の言語でも Codecov みたいなことをしたいなと思ってやったことを共有します。

記事の前提としてあなたは何らかの方法を用いて GitHub Actions でカバレッジを計測することはできていると仮定します。
今回私は Open Policy Agent (OPA) を例にしていますが、この条件を満たしていれば言語には依存しないはずです。

PR へのカバレッジのコメント

結論

name: comment-coverage
on:
  pull_request:
    branches:
      - master
jobs:
  comment-coverage:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Setup
        uses: open-policy-agent/setup-opa@v2
      - name: Export coverage
        run: |
          echo "COVERAGE=$(opa test policy -c | jq .coverage)" >> $GITHUB_ENV
      - name: Format comment
        run: |
          echo "coverage: ${{ env.COVERAGE }}%" >> comment
      - name: Comment coverage to PR
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          URL: ${{ github.event.pull_request.html_url }}
        run: |
          gh pr comment -F ./comment "${URL}"

方針

コメントは GitHub CLI を使って投稿します。
https://cli.github.com/manual/gh_pr_comment

内容は文字列でもファイルでも良いみたいです。

各ステップ

  1. Export coverageステップでカバレッジを計測し、環境変数に追加します
    opa test policy -c | jq .coverageの部分は言語に合わせて変えてください
  2. Format commentステップでは 1 つ前に環境変数に追加したカバレッジを取り出し、コメントの内容を整形します
    ここではcoverage: 90%のようなシンプルな内容ですが、ファイルに書き出す形にしているので内容は自由です
  3. 最後にComment coverage to PRのステップでコメントを投稿します

なお、コメント投稿のステップでは一見GITHUB_TOKENというシークレットを設定しておく必要があるように見えますが不要です。

At the start of each workflow run, GitHub automatically creates a unique GITHUB_TOKEN secret to use in your workflow. You can use the GITHUB_TOKEN to authenticate in a workflow run.

https://docs.github.com/en/actions/security-guides/automatic-token-authentication

結果

このジョブが走ると以下のようなコメントが投稿されます。

README へのバッジの追加

結論

name: coverage-badge
on:
  push:
    branches:
      - master
jobs:
  coverage-badge:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Setup
        uses: open-policy-agent/setup-opa@v2
      - name: Export coverage
        run: echo "COVERAGE=$(opa test policy -c | jq .coverage)" >> $GITHUB_ENV
      - name: Upload JSON
        uses: schneegans/dynamic-badges-action@v1.6.0
        with:
          auth: ${{ secrets.GIST_SECRET }}
          gistID: 0c2e618c502912eff6e83e26b24e5c82
          filename: opa-coverage-badge.json
          label: Coverage
          message: ${{ env.COVERAGE }}%
          valColorRange: ${{ env.COVERAGE }}
          minColorRange: 50
          maxColorRange: 90

方針

バッジの作成には Shields.io がよく使われるのでここでも使いたいです。

Shields.io を利用するためにはカバレッジをどこかに保存しておく必要があります。
また数値に応じて色も変えたいです。
ここでは Dynamic Badges という Action を使います。
Dynamic Badges は Gist に Shields.io リクエスト用の JSON をアップロードします。
またオプショナルなパラメータを設定することでカバレッジに応じた色の計算もしてくれます。
https://github.com/marketplace/actions/dynamic-badges

準備

こちらは準備が必要です。

  1. Gist を作成し、JSON ファイルを追加
  2. GitHub の設定から Gist の権限を持ったトークンを生成
  3. 2.で生成したトークンをリポジトリのシークレットに追加

詳細は Dynamic Badges のドキュメントを参照してください。

各ステップ

  1. コメントの場合と同様にExport coverageステップでカバレッジを計測し、環境変数に追加します

  2. Upload JSONで Dynamic Badges を使って JSON をアップロードします
    必須のパラメータは以下です

    • auth: 準備で作成したシークレット
    • gistID: 準備で作成した Gist の ID
    • filename: 準備で追加した JSON ファイルの名前
    • label: バッジのラベル
    • message: バッジのメッセージ

    色の計算をする場合は valColorRange, minColorRange, maxColorRange も必要になります

結果

このジョブが走ると以下のような JSON が Gist にアップロードされます。

この JSON を Shields.io のリクエストで指定することでカバレッジバッジを表示することができました。
Coverage

余談ですが、Gist も GitHub CLI で編集可能なので色の計算さえ自前でやれば Dynamic Badges は不要です。

追記

Dynamic Badges 使わない版も確認しました。
Dynamic Badges の実装を参考に Bash で HSL を計算し、gh gist editで JSON を更新します。

name: coverage-badge
on:
  push:
    branches:
      - master
  pull_request:
    branches:
      - master
jobs:
  coverage-badge:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Setup
        uses: open-policy-agent/setup-opa@v2
      - name: Export coverage
        run: echo "COVERAGE=$(opa test policy -c | jq .coverage)" >> $GITHUB_ENV
      - name: Calc color
        run: |
          coverage=${{ env.COVERAGE }}
          floor=${coverage:0:2}
          min=50
          max=90
          round=$((max < floor ? max : floor < min ? min : floor))
          hue=$(((round - min) * 120/ (max - min)))
          echo "COLOR=hsl($hue, 100%, 40%)" >> $GITHUB_ENV
      - name: Upload JSON
        env:
          GITHUB_TOKEN: ${{ secrets.GIST_SECRET }}
        run: |
          echo '{"schemaVersion":1,"label":"Coverage","message":"${{ env.COVERAGE }}%","color":"${{ env.COLOR }}"}' >> test.json
          gh gist edit 6641f83484aca3643c1f6d9ddee38260 test.json

まとめ

今回の記事ではそれぞれのジョブを独立したものとして設定しましたが、実際にはテストを 2 回以上実行するのは無駄なので改善の余地があります。
一方でジョブをまとめる場合でもコメントは PR の場合のみ、JSON アップロードは master への push の場合のみで十分ですが、そうなるとステップに対して条件を設定する必要があります。
面倒くさいので Codecov が使えるなら使った方が良いかと。

Discussion