【GameCI】指定した複数のUnityバージョンでテストを走らせるワークフロー【GitHub Actions】

https://twitter.com/su10_dev/status/1585856497803616256?s=20&t=HY2wKgcEu0k8ounCvIkmjw

# はじめに
いろんなUnityバージョンに対応したライブラリを作ってて手元でテストするのが面倒だったので、

* GitHub Actionsで
* Unityのバージョンを複数指定して
* すべてのバージョンでUnity Test Framework（旧:Unity Test Runner）によるテストを走らせる

ようなワークフローを書いたので紹介します。

ちなみに冒頭のツイートの画像の内容だとバグっていて、単一のUnityバージョンでしかテストが走らないのでご注意ください。

# 事前準備
予めUnityのライセンス周りの下準備をしておく必要があります。

自分は以下の記事を参考にしました。

* [GameCI で Unity の CI 環境を GitHub Actions で構築する](https://zenn.dev/nikaera/articles/unity-gameci-github-actions)

複数のUnityバージョンでテストを走らせる場合でも、条件的にPersonalライセンスが使用可能であれば１つでOKです。

# ワークフローを実行する
Unityのライセンス情報をSecretsに登録した状態で↓のファイルを

　`{リポジトリのルート}/.github/workflows/`

以下に置くと、GitHubにpushされたときにテストが実行されます。

```run-test.yml
name: Run Test

on: [push]

jobs:
  test:
    name: Test in Unity${{ matrix.unityVersion }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        unityVersion:
          - 2021.3.0f1  # 好きなバージョンを指定
          - 2022.1.0f1  # 好きなバージョンを指定
    steps:
      - uses: actions/checkout@v3
        with:
          lfs: true
          path: Unity${{ matrix.unityVersion }}
      - uses: actions/cache@v3
        with:
          path: Unity${{ matrix.unityVersion }}/Library
          key: Library-Unity${{ matrix.unityVersion }}
          restore-keys: |
            Library-
      - uses: game-ci/unity-test-runner@5263cf0ab1d1c310c57c1861c71324b7e273909c
        id: tests
        env:
          UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
        with:
          unityVersion: ${{ matrix.unityVersion }}
          projectPath: Unity${{ matrix.unityVersion }}
          artifactsPath: Unity${{ matrix.unityVersion }}-artifacts
          githubToken: ${{ secrets.GITHUB_TOKEN }}
          checkName: Unity${{ matrix.unityVersion }} Test Results
      - uses: actions/upload-artifact@v3
        if: always()
        with:
          name: Test results for Unity${{ matrix.unityVersion }}
          path: ${{ steps.tests.outputs.artifactsPath }}
```

このワークフローを実行すると

* Unity2021.3.0f1
* Unity2022.1.0f1

の２つのバージョンでそれぞれテストが実行されます。

使うときは `# 好きなバージョンを指定` の行を適当に追加/削除/変更してください。

　
そもそもUnity Test Frameworkがわからない場合は、以下が大変参考になります。

* [【Unity】Unity Test Runner（Test Framework）の使い方を総まとめ - インストールから自動化まで](https://light11.hatenadiary.com/entry/2020/07/01/200253)

# GameCIのサンプルコードはちょっと古い
先に紹介したワークフローは [GameCIのドキュメント](https://game.ci/docs/github/test-runner#complete-example) に記載の以下を参考にしてます。

```yml
name: Test project

on: [push, pull_request]

jobs:
  testAllModes:
    name: Test in ${{ matrix.testMode }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        projectPath:
          - test-project
        testMode:
          - playmode
          - editmode
    steps:
      - uses: actions/checkout@v2
        with:
          lfs: true
      - uses: actions/cache@v2
        with:
          path: ${{ matrix.projectPath }}/Library
          key: Library-${{ matrix.projectPath }}
          restore-keys: |
            Library-
      - uses: game-ci/unity-test-runner@v2
        id: tests
        env:
          UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
        with:
          projectPath: ${{ matrix.projectPath }}
          testMode: ${{ matrix.testMode }}
          artifactsPath: ${{ matrix.testMode }}-artifacts
          githubToken: ${{ secrets.GITHUB_TOKEN }}
          checkName: ${{ matrix.testMode }} Test Results
          coverageOptions: 'generateAdditionalMetrics;generateHtmlReport;generateBadgeReport;assemblyFilters:+my.assembly.*'
      - uses: actions/upload-artifact@v2
        if: always()
        with:
          name: Test results for ${{ matrix.testMode }}
          path: ${{ steps.tests.outputs.artifactsPath }}
      - uses: actions/upload-artifact@v2
        if: always()
        with:
          name: Coverage results for ${{ matrix.testMode }}
          path: ${{ steps.tests.outputs.coveragePath }}
```

これを変更して使うにあたってはいくつか注意点があったのでそちらもご紹介します。

## Node.js 12に関する警告が出る
そのままだとワークフロー実行時に以下のような警告が出ます。

>Node.js 12 actions are deprecated. For more information see: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/. Please update the following actions to use Node.js 16: actions/checkout, actions/cache, game-ci/unity-test-runner, actions/upload-artifact, actions/upload-artifact, actions/cache, actions/checkout

警告に従って、使用されている以下のアクションのバージョンを上げます。 `v2` を `v3` に書き換えるだけです。

* `actions/checkout@v2`　　　　 → `actions/checkout@v3`
* `actions/cache@v2` 　　　　　　→ `actions/cache@v3`
* `actions/upload-artifact@v2` → `actions/upload-artifact@v3`

参考：[GitHub ActionsでNode.js 12のサポートが終了します](https://qiita.com/masato_makino/items/3edde8e5f1a782a5c7ea)

## `set-output` に関する警告が出る
まだ以下の警告が出ます。

>The `set-output` command is deprecated and will be disabled soon. Please upgrade to using Environment Files. For more information see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/

参考：[GitHub Actionsで `save-state` と`set-output` が廃止されるようです。](https://diary.masasuzu.net/entry/2022/10/23/190602)

これはGameCIの `game-ci/unity-test-runner@v2` に起因するものなので、こちらのバージョンを上げます。

`game-ci/unity-test-runner` の最新のリリースは現時点で `v2.0.3` なのですが、修正が現状 `main` ブランチにしか入ってない([#193](https://github.com/game-ci/unity-test-runner/pull/193),[#194](https://github.com/game-ci/unity-test-runner/pull/194))ので、最新のコミットのハッシュを直接指定します。

* `game-ci/unity-test-runner@v2`
→ `game-ci/unity-test-runner@5263cf0ab1d1c310c57c1861c71324b7e273909c`

もしこの記事を読んだ時点で `v2.0.4` がリリースされてたらコミットハッシュの代わりにそちらを指定すると良いと思います。

# 注意点
複数のUnityバージョンでテストを回すと実行時間が嵩むので、無料枠で動かす場合は注意が必要です。

参考：
* [About billing for GitHub Actions](https://docs.github.com/ja/billing/managing-billing-for-github-actions/about-billing-for-github-actions)（GitHub Docs）
* [GitHub Actions の利用状況の確認と無料枠の上限に達した場合の対処方法](https://blog.beachside.dev/entry/2021/09/27/190000)
* [GitHub Actions の無料枠を使いきった場合](https://qiita.com/technote-space/items/7b2694786f577c823fc1)

# おまけ：すべてのプラットフォームでコンパイルが通るかどうかのテスト
複数のUnityバージョンで動作を保証するライブラリ/アセット/フレームワークを作っていて、最低限ほしいのは「サポートするUnityバージョンでビルドが通るかどうかのテスト」です。

ということでC#9.0のUnityで動くテストを書いてみました。 [Scriptable Build Pipeline](https://docs.unity3d.com/ja/2019.4/Manual/com.unity.scriptablebuildpipeline.html) のパッケージが必要です。

```CompilationTest.cs
using System.IO;
using NUnit.Framework;
using UnityEditor;
using UnityEditor.Build.Player;

public class CompilationTest
{
    private static readonly string OutputPath = $"Temp/{nameof(CompilationTest)}";

    [Test] public void StandaloneOSX() => Assert.IsTrue(Compile(BuildTarget.StandaloneOSX));
    [Test] public void StandaloneWindows() => Assert.IsTrue(Compile(BuildTarget.StandaloneWindows));
    [Test] public void iOS() => Assert.IsTrue(Compile(BuildTarget.iOS));
    [Test] public void Android() => Assert.IsTrue(Compile(BuildTarget.Android));
    [Test] public void StandaloneWindows64() => Assert.IsTrue(Compile(BuildTarget.StandaloneWindows64));
    [Test] public void WebGL() => Assert.IsTrue(Compile(BuildTarget.WebGL));
    [Test] public void WSAPlayer() => Assert.IsTrue(Compile(BuildTarget.WSAPlayer));
    [Test] public void StandaloneLinux64() => Assert.IsTrue(Compile(BuildTarget.StandaloneLinux64));
    [Test] public void PS4() => Assert.IsTrue(Compile(BuildTarget.PS4));
    [Test] public void XboxOne() => Assert.IsTrue(Compile(BuildTarget.XboxOne));
    [Test] public void tvOS() => Assert.IsTrue(Compile(BuildTarget.tvOS));
    [Test] public void Switch() => Assert.IsTrue(Compile(BuildTarget.Switch));
    [Test] public void Lumin() => Assert.IsTrue(Compile(BuildTarget.Lumin));
    [Test] public void Stadia() => Assert.IsTrue(Compile(BuildTarget.Stadia));
    [Test] public void CloudRendering() => Assert.IsTrue(Compile(BuildTarget.CloudRendering));
    [Test] public void GameCoreXboxSeries() => Assert.IsTrue(Compile(BuildTarget.GameCoreXboxSeries));
    [Test] public void GameCoreXboxOne() => Assert.IsTrue(Compile(BuildTarget.GameCoreXboxOne));
    [Test] public void PS5() => Assert.IsTrue(Compile(BuildTarget.PS5));
    [Test] public void EmbeddedLinux() => Assert.IsTrue(Compile(BuildTarget.EmbeddedLinux));

    private static bool Compile(BuildTarget target)
    {
        var input = new ScriptCompilationSettings
        {
            target = target,
            group = BuildPipeline.GetBuildTargetGroup(target),
        };

        var result = PlayerBuildInterface.CompilePlayerScripts(input, OutputPath);
        var assemblies = result.assemblies;
        var passed = (assemblies is { Count: > 0 } && (result.typeDB != null));

        if (Directory.Exists(OutputPath)) Directory.Delete(OutputPath, true);

        return passed;
    }
}
```

参考：
* [【Unity】各プラットフォームにおいてコンパイルエラーが発生しないか確認できるテストコード](https://baba-s.hatenablog.com/entry/2020/02/05/090000)
* [[Unity] プレイヤービルド時に出るコンパイルエラーを事前にチェックする](https://qiita.com/k7a/items/ef5753e736d288fecc89)
* [【Unity】Scriptable Build Pipelineによるビルドフロー構築入門](https://light11.hatenadiary.com/entry/2021/03/30/201333)

# その他参考になりそうなリンク
* [GitHub Actions でキャッシュを使った高速化](https://www.kaizenprogrammer.com/entry/2019/12/15/220137)
* [GitHub Actions の uses でメジャーバージョンを参照するのはリスク](https://zenn.dev/snowcait/articles/53ec922a414dde)
* [GitHub Actions で簡単にバージョン番号付きリリースとリリースノートを作成する方法](https://zenn.dev/kounoike/articles/20220211-easy-generate-release-notes)
* [複数リポジトリでのGithub Actions運用 2021年の状況総まとめ](https://zenn.dev/jerome/articles/cc07ad73e017ad)
* [ジョブにマトリックスを使用する](https://docs.github.com/ja/actions/using-jobs/using-a-matrix-for-your-jobs)（GitHub Docs）