Unityプロジェクトのビルド&テストを自動化しよう！(GameCIの使い方)

Unityのテストなど自動で行いたいと思いませんか？CI(継続的インテグレーション)を行うことで、コードの変更がリポジトリにプッシュされるたびにテストが行われ、問題やエラーを早期に発見しやすくなる．本記事ではUnityのプロジェクトのビルドとテストをGithubActionを用いて自動化するCIツールである**GameCI**について紹介します．

# CIツールとは
**CI(ContinuousIntegration,継続的インテグレーション)**
ソフトウェア開発プロセスにおける一つの実践方法である．主に自動化されたビルドとテストを用いて，開発されたソフトウェアの品質を維持し，開発プロセスを効率化することを目的としている．


# CIでできること
**自動ビルド**
開発者がコードの変更をバージョン管理システム（例：Git）にコミットすると，CIツールが自動的にソースコードを取得し，ビルドを実行します．これにより，コードが正常にビルドできるかどうかを確認する．

**自動テスト**
ビルドプロセスには自動テスト（単体テスト、統合テストなど）が含まれることが多く，これによって新しいコードが既存のコードベースと正しく統合されているか，また新たな問題が発生していないかを確認する．

**フィードバックループの迅速化**
CIプロセスは，ビルドやテストの結果を迅速に開発者にフィードバックすることを目的としている．これにより，問題が早期に発見され，修正も迅速に行うことができる．

# CIのメリット
**エラーの早期発見と修正**
開発プロセスの早い段階で問題を発見し，修正することができる．

**品質の向上**
定期的なビルドとテストにより，ソフトウェアの品質が維持されます．

**開発の効率化** 
ビルドやテストの自動化により，時間の節約と効率的な開発が可能になる．

**ドキュメントの改善**
CIプロセスを通じて，ビルドやテストの手順が文書化され、新しいチームメンバーがプロジェクトに参加しやすくなる．

# GameCIを動かす設定
Unityのプロジェクトを動かすとする．必要な環境変数は以下の3つである．
 - UNITY_LICENSE
 - UNITY_EMAIL
 - UNITY_PASSWORD

## UnityHubの設定
個人ライセンス(無料)を使っていることを仮定する．

`設定`-`ライセンス`から個人ライセンスを取得する．

## Githubリポジトリに登録する環境変数設定

ライセンスが入っているファイルは以下に格納されている．このファイルの中にあるすべての文字列が`UNITY_LICENSE`である．

Windows: 
```
C:\ProgramData\Unity\Unity_lic.ulf
```
Mac: 
```
/Library/Application Support/Unity/Unity_lic.ulf
```
Linux:
```
 ~/.local/share/unity3d/Unity/Unity_lic.ulf
```

 `UNITY_EMAIL`と`UNITY_PASSWORD`はそのライセンスでのアカウントのログインメールとパスワードである．

 これをgithubリポジトリに登録する．
 `Github` > `<Your repository>` > `Settings` > `Secrets and Variables`のように移動して`Repository secrets`に上記の環境変数を追加する．

 # CIの定義をymlで作成
 一旦完成形を掲載すると以下のようになる．
 ```unity_ci.yml
name: Unity CI

on:
  push:
    branches:
      - development
      - 'feature/*'
  pull_request:
    branches:
      - development

env:
  WORKING_DIRECTORY: .
  UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
  UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
  UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}

jobs:
  test:
    name: Test in ${{ matrix.testMode }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        testMode:
          - playmode
          - editmode
          - standalone
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Create LFS file list
        run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id
        working-directory: ${{ env.WORKING_DIRECTORY }}

      - name: Restore LFS cache
        uses: actions/cache@v3
        with:
          path: ${{ env.WORKING_DIRECTORY }}/.git/lfs
          key: ${{ runner.os }}-lfs-${{ hashFiles('${{ env.WORKING_DIRECTORY }}/.lfs-assets-id') }}

      - name: Git LFS Pull
        run: |
          git lfs pull
          git add .
          git reset --hard
        working-directory: ${{ env.WORKING_DIRECTORY }}

      - uses: actions/cache@v3
        with:
          path: ${{ env.WORKING_DIRECTORY }}/Library
          key: Library-${{ hashFiles('${{ env.WORKING_DIRECTORY }}/Assets/**', '${{ env.WORKING_DIRECTORY }}/Packages/**', '${{ env.WORKING_DIRECTORY }}/ProjectSettings/**') }}
          restore-keys: |
            Library-

      - uses: game-ci/unity-test-runner@v4
        with:
          testMode: ${{ matrix.testMode }}
          artifactsPath: ${{ matrix.testMode }}-artifacts
          githubToken: ${{ secrets.GITHUB_TOKEN }}
          checkName: ${{ matrix.testMode }} Test Results
          coverageOptions: 'generateAdditionalMetrics;generateHtmlReport;generateBadgeReport;assemblyFilters:+my.assembly.*'
          working-directory: ${{ env.WORKING_DIRECTORY }}

      - uses: actions/upload-artifact@v3
        if: always()
        with:
          name: Test results for ${{ matrix.testMode }}
          path: ${{ env.WORKING_DIRECTORY }}/${{ steps.tests.outputs.artifactsPath }}

      - uses: actions/upload-artifact@v3
        if: always()
        with:
          name: Coverage results for ${{ matrix.testMode }}
          path: ${{ env.WORKING_DIRECTORY }}/${{ steps.tests.outputs.coveragePath }}
```
このymlファイルの説明をする．
## on
`on`キーワードはGitHub Actionsワークフローのトリガーを指定します。どのGitHubリポジトリのイベント（プッシュ、プルリクエスト、リリースなど）に対してワークフローが実行されるかを定義する．これはdevelopmentブランチやfeature/から始まるブランチにプッシュした場合と，developmentブランチにプルリクエストを送る場合にCIが動くようになる．

```yaml
on:
  push:
    branches:
      - development
      - 'feature/**'
  pull_request:
    branches:
      - development
```

## env
`env`キーワードはワークフロー内で使用される環境変数を宣言する．これにより，ジョブやステップ間で共通の環境設定を一元管理できる．
ここではUnityプロジェクトのルートディレクトリと先ほど取得したUnityライセンスやログイン情報を指定する．
```yaml
env:
  WORKING_DIRECTORY: dir
  UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
  UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
  UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
  
```
:::note info
envで設定された環境変数はワークフロー内のすべてのジョブやステップで利用可能である．
:::



## test
このtestの目的はUnityプロジェクトのテストを自動化し，問題を早期に発見すること．

- **リポジトリのチェックアウト**:
  - `actions/checkout@v4`を使用して，リポジトリのコードを`dementia_quiz_VR`というディレクトリにチェックアウトする
- **LFSファイルリストの作成**:
  - `git lfs ls-files`コマンドを使用して，LFSで管理されているファイルのリストを生成し，`.lfs-assets-id`として保存する
- **LFSキャッシュの復元**:
  - `actions/cache@v3`を使用して，LFSキャッシュを復元します。キャッシュのキーはLFSファイルリストのハッシュに基づいている
- **Git LFS プル**:
  - 必要なLFSオブジェクトをダウンロードし，リポジトリを最新の状態にリセットする
- **ライブラリキャッシュの復元**:
  - Unityプロジェクトの`Library`フォルダのキャッシュを復元する
- **テストの実行**:
  - `game-ci/unity-test-runner@v4`を使用して，指定されたテストモード（playmode、editmode、standalone）でテストを実行する
- **テスト結果のアップロード**:
  - テストが完了した後，生成されたアーティファクト（テスト結果やカバレッジ結果）をアップロードする

これをプッシュすると自動的にGithubActionが動くのでCIが動くようになる．