はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity2024 Advent Calendar】Unityのコードメトリクスを可視化するための分析基盤の作り方(CodeCoveragePackage + coverlet.collector + octocov + BigQuery + Looker Studio)

はじめに

こちらはUnity2024 Advent Calendarの12/20記事になります。是非他の方の記事もチェックしてみてください。
qiita.com

今回はUnityでのコードメトリクスを可視化するための分析基盤の作り方について紹介したいと思います。

PRのコメントでコードメトリクスを表示してくれる
コードメトリクスの遷移を可視化した様子

Code Coverageとは

Code Coverageとはテストで用いられるコードがどれだけ実行されたかを示す指標です。テストケースがあまり網羅されていないコードを検出することに役に立ちます。

コード網羅率(コードもうらりつ、英: Code coverage、コードカバレッジ)は、ソフトウェアテストで用いられる尺度の1つである。プログラムのソースコードがテストされた割合を意味する。この場合のテストはコードを見ながら行うもので、ホワイトボックステストに分類される。

コード網羅率は体系的なソフトウェアテストのための技法として最初に生み出されたものの1つである。1963年の Communications of the ACM にある Miller と Maloney の論文に言及されているのが最初である。

コード網羅率 - Wikipedia

構成概要

具体的には以下の構成で構築しました。

構成図

主に以下を利用しています。

  • CodeCoverage Package
  • coverlet.collector
  • octocov
  • BigQuery
  • Looker Studio

それぞれについては後述します。

Unity公式パッケージ「CodeCoverage」

Unityの公式パッケージであるCode Coverage Packageを利用することで、コードカバレッジを取得することができます。
docs.unity3d.com

またUnityに依存していないコードについてはdotnet testによる計測ができないかは一考の余地があると思います。Unityに依存するコードは面倒くさいことが多くイテレーションも遅いので、なるべくUnityに依存するコードとUnityに依存しないコードをアセンブリレベルで分離したほうが良いでしょう。

dotnet testを用いたコードカバレッジの収集方法は下記に記載しています。
www.hanachiru-blog.com

セットアップ方法

Package Managerからインストールします。Add Package from git URL...からcom.unity.testtools.codecoverageと入力すればOKです。

UPMでcom.unity.testtools.codecoverageをインストール

実行方法

コードカバレッジはCIで収集するケースがほとんどだと思うので、Unityのバッチモードでテストを実行 + カバレッジ収集を行います。
Using Code Coverage in batchmode | Code Coverage | 1.2.6

# MacでUnity 6000.0.23f1の場合
# generateAdditionalReportsを付与することで、SonarQube, Cobertura and LCOV形式で出力する
$ /Applications/Unity/Hub/Editor/6000.0.23f1/Unity.app/Contents/MacOS/Unity  \
            -projectPath <path-to-project-path> \
            -batchmode \
            -testPlatform editmode \
            -runTests \
            -debugCodeOptimization \
            -enableCodeCoverage \
            -burst-disable-compilation \
            -coverageResultsPath <path-to-codecoverage-result> \
            -coverageOptions "generateAdditionalReports;"

↓実際に実行した例

$ tree
<path-to-codecoverage-result>
├── <ProjectName>-opencov
│   └── EditMode
│       ├── TestCoverageResults_0000.xml
│       └── TestCoverageResults_0001.xml
└── Report
    ├── Cobertura.xml
    ├── SonarQube.xml
    ├── Summary.json
    ├── Summary.md
    ├── Summary.xml
    └── lcov.info
アセンブリをフィルタする

コードカバレッジを計測するアセンブリを絞りたいことはよくあると思います。assemblyFiltersを用いることで含めるアセンブリを絞ることができます。+は含めて、-は除外です。

# assemblyFiltersにより計測するアセンブリをフィルタする
# "Hoge."でアセンブリ名が始まるものを対象にし、"Hoge.Samples."でアセンブリ名が始まるものは計測対象外にする(Globにより指定)
$ /Applications/Unity/Hub/Editor/6000.0.23f1/Unity.app/Contents/MacOS/Unity  \
            -projectPath . \
            -batchmode \
            -testPlatform editmode \
            -runTests \
            -debugCodeOptimization \
            -enableCodeCoverage \
            -burst-disable-compilation \
            -coverageResultsPath <path-to-codecoverage-result> \
            -coverageOptions "generateAdditionalReports;assemblyFilters:+Hoge.*,-Hoge.Samples.*"

またpathFiltersなどもありますので、気になる方は公式ドキュメントを参照してください。

HTML Reportを生成する

CodeCoverage Packageでは下記のようなHTMLレポートを生成することができます。具体的にどのLineがテスト実行されていないか調べるケースでは有用なので、GitHubのArtifactやGCSなどにあげてPRを送ったタイミングでみれるようにするとかはアリかなと思います。

公式ドキュメントより引用
# generateHtmlReportを追加する
$ /Applications/Unity/Hub/Editor/6000.0.23f1/Unity.app/Contents/MacOS/Unity  \
            -projectPath . \
            -batchmode \
            -testPlatform editmode \
            -runTests \
            -debugCodeOptimization \
            -enableCodeCoverage \
            -burst-disable-compilation \
            -coverageResultsPath <path-to-codecoverage-result> \
            -coverageOptions "generateAdditionalReports;generateHtmlReport"

octocovを利用してGitHubに通知を行う

octocovコードメトリクスを収集するためのツールキットです。今回は GitHubにコードメトリクスをコメントで通知する + Big Queryにデータをアップロードするために利用します。

octocov is a toolkit for collecting code metrics (code coverage, code to test ratio, test execution time and your own custom metrics).

// DeepL翻訳
octocovは、コード・メトリクス(コード・カバレッジ、コード対テスト比、テスト実行時間、独自のカスタム・メトリクス)を収集するためのツールキットです。

github.com

カバレッジレポートのフォーマットは沢山あるのですが、octcovは以下に対応しています。

  • Go coverage
  • LCOV
  • SimpleCov
  • Clover
  • Cobertura
  • JaCoCo

UnityのCodeCoverageLCOVを吐けるので、LCOVを利用すると良いでしょう。またcoverlet.collectorを利用したdotnet testでもLCOVを吐けます。

設定ファイルの作成

octocovには設定ファイルを利用することで様々な設定ができます。場所はどこでも良いのですが.octocov.ymlファイルを作成して中身を記述してください。

詳細はreadmeを見てみて欲しいのですが、私がよく利用する設定を載せておきます。
github.com

# .octocov.yml

# lcov.infoを読み込む(UnityのCode Coverage Packageで出力先をCodeCoverageに設定する)
coverage:
  paths:
    - CodeCoverage/Report/lcov.info
      
# テストの実行時間として利用するsteps (Actions側でnameを'Unity Test'にしている場合)
testExecutionTime:
  steps:
    - 'Unity Test'

# codeとtestの割合を計算するのに利用するファイル
codeToTestRatio:
  code:
    - '**/*.cs'
    - '!**/Tests/**/*.cs'
  test:
    - '**/Tests/**/*.cs'
      
# プルリクエストにコメントをつける
comment:
  if: is_pull_request
  hideFooterLink: true

# JobのSummaryを表示する
summary:
  if: true
  
# 差分を表示するためにArtifactを利用(後ほどBig Queryに置き換える)
report:
  if: is_default_branch
  datastores:
    - artifact://${GITHUB_REPOSITORY}/dotnet-test-report
      
diff:
  datastores:
    - artifact://${GITHUB_REPOSITORY}/dotnet-test-report

GitHub Actionsのワークフロー作成

Unityでテストを実行して、octocovでPRにコメントをするワークフローを作成します。

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

jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
      actions: read
      checks: write
    env:
      LCOV_DIR: ${{ github.workspace }}/CodeCoverage
    steps:
      - uses: actions/checkout@v4
      - name: Unity Test
        step:
          run: |
            # ここで下記のようにUnityをバッチモードで立ち上げてテストを実行しなければならない
            # 以下の書き方だと動作しないので注意してください
            /Applications/Unity/Hub/Editor/6000.0.23f1/Unity.app/Contents/MacOS/Unity  \
              -projectPath . \
              -batchmode \
              -testPlatform editmode \
              -runTests \
              -debugCodeOptimization \
              -enableCodeCoverage \
              -burst-disable-compilation \
              -coverageResultsPath "$LCOV_DIR" \
              -coverageOptions "generateAdditionalReports;"
      - uses: k1LoW/octocov-action@v1
        with:
          config: .octocov.yml
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_REPOSITORY: ${{ github.repository }}

ただしUnityを用意しないといけないのですが、SelfHosted Runnerを個人で用意するのはかなり大変です。そこで利用させていただくのがGame CIです。
www.hanachiru-blog.com

Game CIの利用

Game CI側でUnityを用意してくれているので、それをありがたく利用させていただきます。セットアップの仕方は先ほど貼った記事に書いてあるので省くのですが、game-ci/unity-test-runner@v4を使う際にinputscoverageOptionsを用意してくれています。

github.com
Code Coverage Options With Combined Coverage Results · Issue #181 · game-ci/unity-test-runner · GitHub
github action check status is "neutral" when tests pass · Issue #220 · game-ci/unity-test-runner · GitHub

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

jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
      actions: read
      checks: write
    steps:
      - uses: actions/checkout@v4
      - name: Unity Test
        uses: game-ci/unity-test-runner@v4
        env:
          UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
          UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
          UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
        with:
          githubToken: ${{ secrets.GITHUB_TOKEN }}
          unityVersion: 6000.0.23f1
          # PlayModeとEditModeのTestを実行
          testMode: all
          # CodeCoverageの出力先を指定できないよう(-coverageResultsPathでも制御できなかった)なので、CodeCoverage/Report/lcov.infoに固定で出力されているので使う
          coverageOptions: generateAdditionalReports;dontClear
          customParameters: -debugCodeOptimization -enableCodeCoverage -burst-disable-compilation
      - uses: k1LoW/octocov-action@v1
        with:
          config: .octocov.yml
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_REPOSITORY: ${{ github.repository }}      

またaction.ymlのoutputにcoveragePathを用意してくれているので、それを使うこともできます。ただoctcov側に環境変数を渡せなかった(多分)ので、CodeCoverage/Report/lcov.infoを直書きしてます。また-coverageResultsPathを用いても出力先を制御できなさそうでした。
github.com

# 利用する例
- uses: actions/upload-artifact@v3
  if: always()
  with:
    name: Coverage results
    path: ${{ steps.myTestStep.outputs.coveragePath }}

Test runner | GameCI

動作している様子

正しく動作していれば、PRを作成すると以下のような表示がなされるはずです。

PRのコメントでコードメトリクスを表示してくれる

BigQueryの構築

BigQueryはGCPが提供するデータ分析プラットフォームです。今回はコードメトリクスデータを保存するために利用します。

GCPのサービスアカウントの作成

octocovからBig Queryへコードメトリクスデータを送るにあたって、まずはサービスアカウントを作成する必要があります。
cloud.google.com

権限をどうするかはちゃんとご自身で調べて欲しいですが、私の場合は以下を利用しました。

  • roles/bigquery.jobUser
  • roles/bigquery.dataEditor

https://cloud.google.com/bigquery/docs/access-control?hl=ja


またどんな権限が必要かはoctcovのreadmeに載っています。

  • bigquery.datasets.get
  • bigquery.tables.get
  • bigquery.tables.updateData
  • bigquery.jobs.create
  • bigquery.tables.getData

https://github.com/k1LoW/octocov?tab=readme-ov-file#bigquery
https://github.com/k1LoW/octocov?tab=readme-ov-file#use-bigquery-table-as-datastore

サービスアカウントが無事作成できたら、鍵(JSON)を作成しておきましょう。

鍵を作成している様子

作成できたら、GitHubのsecretsにGOOGLE_APPLICATION_CREDENTIALS_JSONなどとして入れておきましょう。

Big Queryであらかじめデータセットを用意しておく

GCPのBigQuery上にあらかじめデータセットを用意しておきます。コンソール上でも良いですし、コマンドでも好きなように作成してください。

データセットを用意している様子

テーブルは後ほど作成するので、自身で定義しなくてOKです。

作成したテーブルに情報を追加していくように、先ほどGitHub Artifactにあげるように設定した.octocov.ymlをBig Queryにあげるように修正します。bq://[project ID]/[dataset ID]/[table]のように指定してください。

# .octocov.yml

...
# 差分を表示するためにBig Queryを利用する
# bq://[project ID]/[dataset ID]/[table]
report:
  if: is_default_branch
  datastores:
    - bq://hogehogehoge/octocov_sandbox/reports

diff:
  datastores:
    - bq://hogehogehoge/octocov_sandbox/reports

tableの名前は好きに決めちゃってしまってOKです。

またGitHub上で実行するためにはGOOGLE_APPLICATION_CREDENTIALS_JSONを先ほど取得したサービスアカウントの鍵として設定する必要があります。

- uses: k1LoW/octocov-action@v1
  with:
    config: .octocov.yml
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    GITHUB_REPOSITORY: ${{ github.repository }}
    GOOGLE_APPLICATION_CREDENTIALS_JSON: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS_JSON }}

https://github.com/k1LoW/octocov?tab=readme-ov-file#bigquery

Big Queryにてテーブルのmigrateをする

migrateするにあたって、CLIツールをインストールしておきます。

$ brew install k1LoW/tap/octocov

BigQueryを活用するためにはテーブルのmigrateが必要になります。.octocov.ymlがカレントディレクトリに含まれる状態で以下のコマンドを叩きます。

$ octocov migrate-bq-table

また環境変数等の設定も必要なので、雑に以下のようなPowerShellを用意しておきました。(私は生粋のPowerShell教なので、bash/zshは書きません)

#!/usr/bin/env pwsh
#Requires -Version 7.4

$PSNativeCommandUseErrorActionPreference = $true
$ErrorActionPreference = "Stop"

$env:GOOGLE_APPLICATION_CREDENTIALS_JSON = @"
{
  "type": "service_account",
  ...
}
"@

octocov migrate-bq-table


具体的なSchemaは下記に載っています。
octocov/docs/bq/schema/reports.md at main · k1LoW/octocov · GitHub

Looker Studioにより可視化する

Looker Studioを用いることで、簡単にBig Queryに保存したデータの可視化をすることができます。詳細は書きませんが、直感的にわかるように作られているのでボタンをポチポチしていたらできると思います。

cloud.google.com

データのレポートへの追加からBigQueryのテーブルを選択する
コードメトリクスの遷移を可視化した様子

結果

PRを出す度にテスト + コードメトリクス計測を行い、その結果をGitHub上で表示してBigQueryにあげることができました。またその推移をLooker Studio上で確認することができます。さらにHTMLレポートもどこかにホスティングしていい感じにみれるようにすれば、どのLineが具体的にテストが通っていないかの確認もできます。

PRのコメントでコードメトリクスを表示してくれる
コードメトリクスの遷移を可視化した様子

考察

分析基盤を構築した後、これをどうやって活用していくのかもとても重要な要素かと思います。正直私自身もまだ模索している中です。

特にゲームクライアントだとテスト自体あまり活用されていないことも多々あるのかなと思うので、是非上手に活用してみてください。