【Unity】Code Coverage Package を Github Actions で動かしてみた

# はじめに
Code Coverrage PackageなるものがUnityの中でパッケージ化されていることを皆さんご存じでしたか？自分は知らなかった...
使い方自体はUnityの[公式ブログ](https://blog.unity.com/ja/technology/code-coverage-spot-gaps-in-your-tests)で説明されているので、自分の記事では主にCIを回したときにCodeCoverageが表示されるように自動化するにはどのようにすればよいのかを記事にしたいと思います。
# Code Coverageとは？

Code Coverageは、ソフトウェアテストの重要な指標であり、コードがどの程度網羅的にテストされているかを示します。Code Coverageは、品質向上の指標とすることができます。

主なカバレッジの種類には以下があります：

- 関数カバレッジ: 定義済み関数の呼び出し回数。
- ステートメント カバレッジ: 実行されたステートメントの数。
- ブランチ カバレッジ: 実行されたコントロール構造のブランチの数（例: if ステートメント）。
- 条件カバレッジ: true および false の値にテストされたブール値のサブ式の数。
- 行カバレッジ: テストされたソースコード行の数。

これらのカバレッジの種類は、異なる側面からコードのテスト状況を評価するのに役立ちます。Code Coverageを高めることは、信頼性の高いソフトウェアの開発に寄与します。
(引用：https://www.atlassian.com/ja/continuous-delivery/software-testing/code-coverage)
# テスト用プロジェクト
[GitHub](https://github.com/rabbit-go/CodeCovTest/blob/master/Assets/scripts/Tests/TargetTestCode.cs)上に今回の記事を試した部分について載せておきます。
### テスト対象とテストコード
今回はこの簡単なコードのCoverageを計測することを目標とします。
``` csharp:TargetCode.cs
    public void TestFunc(bool test1, bool test2)
    {
        if (test1)
        {
            Debug.Log("test1");
        }
        else if (test2)
        {
            Debug.Log("test2");
        }
        else
        {
            Debug.Log("else");
        }
    }
```

テスト側のコードは以下となります

``` csharp:TargetCode.cs
using NUnit.Framework;
public class TargetTestCode
{
    // A Test behaves as an ordinary method
    [Test]
    public void Target_false_false()
    {
        TargetCode targetCode = new TargetCode();
        targetCode.TestFunc(false, false);
    }
    [Test]
    public void Target_false_true()
    {
        TargetCode targetCode = new TargetCode();
        targetCode.TestFunc(false, true);
    }
    [Test]
    public void Target_true_false()
    {
        TargetCode targetCode = new TargetCode();
        targetCode.TestFunc(true, false);
    }
}
```
今回はGame側のコードがGameCodeというAssembly Definitionで区切られていることを前提とします。
``` folder構成
Assets---Scripts----------------Tests
            |--GameCode.asmdef   |--Tests.asmdef
            |--TargetCode.cs     |--TargetTestCode.cs
```
# CI環境構築
UnityでCIを行う方法はいくつかありますが、今回は[Game CI](https://game.ci/)というUnity の CI 環境を構築するためのライブラリでUnityのテストをGithubAction上で実行します。
``` yml:test.yml
name: Test project

on: [pull_request]

jobs:
  testAllModes:
    name: Test in ${{ matrix.testMode }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        projectPath:
          - ./
        testMode:
          - playmode
          - editmode
          - standalone
    steps:
      - uses: actions/checkout@v4
      - uses: actions/cache@v3
        with:
          path: ${{ matrix.projectPath }}/Library
          key: Library-${{ matrix.projectPath }}
          restore-keys: |
            Library-
      - uses: game-ci/unity-test-runner@v4
        id: tests
        env:
          UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
          UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
          UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
        with:
          projectPath: ${{ matrix.projectPath }}
          testMode: ${{ matrix.testMode }}
          artifactsPath: ${{ matrix.testMode }}-artifacts
          githubToken: ${{ secrets.GITHUB_TOKEN }}
          checkName: ${{ matrix.testMode }} Test Results
      - uses: actions/upload-artifact@v3
        if: always()
        with:
          name: Test results for ${{ matrix.testMode }}
          path: ${{ steps.tests.outputs.artifactsPath }}
```
基本的なCI用のコードに、unity-test-runnerを指定した形ですね、UNITY_SERIALなどはリポジトリのSecretに登録する必要があります。
### 結果
それぞれのTestModeごとにテストが実行され、結果が表示されます。とても便利！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124028/d37b07ab-017c-4c98-c456-29c9afd26894.png)

## Unity側の設定
Code Coverageを出力するためにはUnity側での設定が必要になります。
Code CoveragePackageを導入していない場合はPackage Manager上から導入できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124028/3790b233-ff2b-9c84-ed18-8213743533e3.png)

導入が完了したらWindow/Analsis/Code Coverageで設定画面を出します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124028/a47ef034-39fc-a116-44d3-b5e59ef143a4.png)
設定画面ではEnable Code Coverageを有効化し、テストを行いたいAssemblesを選択します。
今回の場合はGameCodeとTestsを含める設定にしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124028/f651fe5f-231f-1f91-b4ca-de6559d4b02f.png)
設定が完了したら、GameCI側の設定を変更します。
GameCIはUnityのCode Coverageを出力するオプションを用意しているのでとても簡単に出力できます

:::note info
GameCIを利用しない場合にはUnityをBatchModeで起動する際に引数を指定することでCode Coverageを出力することが可能なはずです(未確認)
(参考：https://docs.unity3d.com/Packages/com.unity.testtools.codecoverage@1.1/manual/CoverageBatchmode.html)
:::
## Code Coverage出力
GithubActionのymlコードを以下のように変更します。
``` yml:test.yml
name: Test project

on: [pull_request]

jobs:
  testAllModes:
    name: Test in ${{ matrix.testMode }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        projectPath:
          - ./
        testMode:
          - playmode
          - editmode
          - standalone
    steps:
      - uses: actions/checkout@v4
      - uses: actions/cache@v3
        with:
          path: ${{ matrix.projectPath }}/Library
          key: Library-${{ matrix.projectPath }}
          restore-keys: |
            Library-
      - uses: game-ci/unity-test-runner@v4
        id: tests
        env:
          UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
          UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
          UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
        with:
          projectPath: ${{ matrix.projectPath }}
          testMode: ${{ matrix.testMode }}
          artifactsPath: ${{ matrix.testMode }}-artifacts
          githubToken: ${{ secrets.GITHUB_TOKEN }}
          checkName: ${{ matrix.testMode }} Test Results
          coverageOptions: 'generateHtmlReport;generateAdditionalReports'
      - uses: actions/upload-artifact@v3
        if: always()
        with:
          name: Test results for ${{ matrix.testMode }}
          path: ${{ steps.tests.outputs.artifactsPath }}
      - uses: actions/upload-artifact@v3
        if: always()
        with:
          name: Coverage results for ${{ matrix.testMode }}
          path: ${{ steps.tests.outputs.coveragePath }}
      - name: Add Coverage PR Comment
        uses: marocchino/sticky-pull-request-comment@v2
        if: github.event_name == 'pull_request' && hashFiles('CodeCoverage/**/Summary.md') != ''
        with:
          recreate: true
          path: CodeCoverage/**/Summary.md
```
変更点としては、unity-test-runner部分にcoverageOptionsを追加しています。
このオプションを追加することで、generateHtmlReportはHTML形式で詳細なCoverageReportを出力し、generateAdditionalReportsは SonarQube, Cobertura や LCOV reports形式で、Buildの詳細なCoverageReportを出力しています。
Add Coverage PR Comment部分はCoverageの要約がSummary.mdという形でCodeCoverage配下に出力されているため、その結果をPRのコメントとして載せています。

### 結果
![結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124028/50d8d2ba-13e6-273c-384f-c5a7a3a57e40.png)

## 結論
GameCIがかなりイケてる！今回はGameCIを利用して楽にCodeCoverageを出力しましたが、原理的には利用しなくてもUPMからパッケージを入れて、起動オプションから出力する同ことは可能なので、ぜひ活用してみましょう。