Unityプロジェクトにおけるプルリクエスト時の自動テスト

本記事は [QualiArts Advent Calender 2020](https://qiita.com/advent-calendar/2020/qualiarts) 9日目の記事です。

昨日は @asakuranobuharu さんの [QUBEという取り組み](https://qiita.com/asakuranobuharu/items/15a82ea0017358207f37) でした。
明日は @Koheinimi さんの 新規開発ノウハウのデータベース化 です。

# はじめに

筆者が所属しているQualiArtsのUnityプロジェクトでは、GitHubでプルリクエストが作成されたときにJenkinsで自動的にテスト（以降、PRテスト）を行う仕組みを作っています。
この記事では、本PRテストの中で具体的にどのようなテストを行っているかを紹介します。
本PRテストではJenkinsでテストを実行していますが、他のCIツールの場合でもどのようなテストをするかのアイディアは活かせるかと思います。
UnityのバージョンはUnity2020.1で試しています。

# PRテストの実現方法

本PRテストを実現するため、Jenkinsのプラグインの1つであるPull Request Builder Pluginを使っています。

導入方法に関しては、
https://qiita.com/kompiro/items/a097b3b36aa30bc751c6
が参考になります。

このプラグインを使えば、特定のリポジトリでプルリクエストが作成されたときに自動的にジョブが実行され、結果がプルリクエストにコメントの形でフィードバックされるようになります。
またテストに通ったかどうかはプルリクエストのチェックステータスにも反映されます。
テストが通ると次の図のように表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81323/6a960880-52b6-3eac-7358-8383af974af6.png)

テストに通らなかったら修正を加えてプッシュすることになりますが、修正がプッシュされるとまた自動的にテストが実行され結果が返ります。
テストに通ったら（加えて、所定の人数の承認が得られたら）マージ可能というルールにしています。


# PRテストの内容

それでは早速、本PRテストで行っている具体的なテストの内容（の一部）を紹介していきます。

## コンパイルエラー、警告のチェック

まず、プルリクエストのブランチにおいてプロジェクトをUnityで開き、コンパイルされた結果エラーまたは警告が出たらテストが失敗になるようにしています。
そのためにはコンパイルエラーと警告を抽出する必要がありますが、うまく抽出する方法が分からなかったのでUnityのログから無理やり抽出するという方法をとっています。

具体的には、まず次のようにUnityをコマンドラインから実行し、プロジェクトを開いて `Editor.log` にログを出力します。
（コマンドはイメージなので実際とは多少異なります。）

```bash
$ /path/to/Unity -batchMode -quit -logFile Editor.log -projectPath TestProject
```

コンパイルエラーや警告の例を示すために、次のようなコードをプロジェクトに入れておきました。

```cs:HogeScript.cs
using UnityEngine;

public class HogeScript : MonoBehaviour
{
    void Start()
    {
        int unused;
        Debug.Log(fuga);
    }
}
```

コンパイルエラーや警告が起きると、次のようなログが`Editor.log`に出力されます。

```text
Assets/HogeScript.cs(8,19): error CS0103: The name 'fuga' does not exist in the current context
Assets/HogeScript.cs(7,13): warning CS0168: The variable 'unused' is declared but never used
```

fugaという名前は存在しないというエラーと、unusedという変数は使われていないという警告です。
ログからこのようなエラーや警告を正規表現等を使って抽出し、何らかのエラーや警告が出ていたらテスト失敗とします。

実際にエラーや警告が発生してテストが失敗した場合、次の画像のようにプルリクエストのコメントにメッセージを載せます（画像は一部改変しています）。
<img width=800 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81323/c8df5130-9f56-251f-9e1b-34df361fa69e.png>


ただし、警告に関しては外部ライブラリなどで引っかかってしまうことがあるので、テストを失敗にする対象となるファイルはプロジェクト固有のファイルに絞っています。
警告すら許さないというのはやり過ぎに思えるかもしれませんが、asyncメソッドをawaitしていないパターンなどの発見に役立っています。

## コーディング規約のチェック

少し前の記事になりますが、筆者は過去に次のような記事を投稿しました。
[C\#静的解析によるコーディング規約チェッカーを作った話 \| CyberAgent Developers Blog](https://developers.cyberagent.co.jp/blog/archives/4257/)

この記事の中で、NRefactoryというライブラリでC#コードを静的解析し、命名規則などのコーディング規約をチェックする仕組みを紹介しています。
現在NRefactoryはメンテナンスされておらず、C#コード解析ライブラリとしてはMicrosoft製のRoslynが標準になっているため、この記事で実装していたコーディング規約チェッカーもRoslynに移行しています。

そのRoslyn製のコーディング規約チェッカーを本PRテストの中に入れており、対象のプルリクエストにおいて差分のあるファイルに対してのみ構文解析によるチェックを行っています。
ソリューション全体を読み込んで意味解析まで行えば、より柔軟で詳細なチェックも可能になるはずですが、そちらは今後の課題としております。

## Unity Test Runnerによるテスト

Unityには Unity Test Runnerという、単体テストを実行するための仕組みが備わっています。
本PRテストでは、このUnity Test Runnerによるテストも走らせています。

Unity Test Runnerに関して詳しくは、
[【Unity】Unity Test Runner（Test Framework）入門 \- インストールから基本的な使い方・注意点まとめ \- LIGHT11](https://light11.hatenadiary.com/entry/2020/02/21/212657)
などを参照ください。
Unity Test RunnerにはEdit ModeテストとPlay Modeテストが存在し、それぞれUnityエディタをPlayしていない状態でのテストとPlayしている状態でのテストを意味します。


例えば、次のようなコードを記述すればUnity Test Runnerのウィンドウからテストを実行できます。

```cs:NewTestScript.cs
using NUnit.Framework;

namespace Tests
{
    public class NewTestScript
    {
        [Test]
        public void HogeTest()
        {
            Assert.IsTrue(false); // テスト失敗！
        }
    }
}
```

↓Unity Test Runnerのウィンドウ
<img width=400 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81323/bfa4618f-8024-d722-d221-ee1e5ef333d3.png>

このUnity Test Runnerによるテストですが、コマンドラインから実行することもできます。

```sh
# Edit Modeテスト
$ /path/to/Unity -batchMode -projectPath TestProject -runEditorTests -editorTestsResultFile EditModeTestResult.xml
# Play Modeテスト
$ /path/to/Unity -batchMode -projectPath TestProject -runTests -testPlatform PlayMode -testResults PlayModeTestsResult.xml
```

上記のコマンドにより、Edit ModeテストとPlay Modeテストを実行でき、実行結果がコマンドで指定したファイル（`EditModeTestResult.xml`と`PlayModeTestsResult.xml`）に出力されます。
このコマンドの結果をもとにテスト結果を判定し、失敗した場合は具体的にどの項目で失敗したかの情報をエンジニアに提供します。

### Unity Test Runnerによる単体テストの中身

Unity Test Runnerでテストを行うためには、当然ながらどのようなテストを行うか自分たちで記述する必要があります。
ゲームクライアント開発では特に顕著な問題だと思うのですが、テストを書くこと自体が高コストであったり、仕様変更が多かったり、そもそも処理が複雑に絡み合いすぎてテストが意味をなす状況が少なかったりという問題あるため、テストを上手に書く難易度は高いのが現実です。

筆者が所属しているプロジェクトでは、下記のような条件を満たしたテストが記述されているイメージです。

- （ほぼ）単純なロジックのみで閉じた処理
- 今後何か手を入れたときにバグる可能性がありそうで不安な処理
- エンジニアにテストを書くモチベーションと時間がある

具体的に実装されているテストとしては、文字列の単純な変換ロジックや、ゲーム内のパラメータ計算や、アセットのロード処理などがあります。

## Missing参照を持つプレハブの検出

本PRテストでは、Missing参照を持つプレハブの検出も行っています。
ここでMissing参照とは、プレハブにシリアライズされた値の中で参照先が不在になってしまっているもののことを指します。
例えばローカルで作業していたときには画像を参照していたがその画像をコミットし忘れたときなどにMissing参照が生まれます。

次の画像の赤枠で囲まれた部分のようにインスペクターでMissingと表示されている状態がMissing参照になっている状態です。
<img width=700 src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/81323/5e478188-fb03-f13a-83b2-971f0d1b05d9.png>


Missing参照の検出方法は、
[【Unity】2018\.3 で Missing References が検出できなくなった時の対処方法 \- コガネブログ](https://baba-s.hatenablog.com/entry/2019/03/20/141500)
を参考にしています。
この記事にあるように、Unity 2018.3以降では従来の検出方法ではうまくいかなくなる可能性があるので注意が必要です。

本PRテストでは、このMissing参照の検出を、対象のプルリクエストで差分のあったプレハブ（内の全てのコンポーネント）に対して行っています。
差分のあったプレハブに対してのみに限定しているのはPRテストの実行時間を短縮するためですが、これだけではプロジェクト内のMissing参照を完全に検出することはできません。
そのため、プロジェクト内のMissing参照を完全に検出したいということなら、毎日1回バッチ処理のような形でプロジェクト全体のプレハブを対象にしたMissing参照の検出を行って結果をSlack等に通知するような仕組みを作ると良いと思います。

# PRテストの効果

これまで述べてきたPRテストですが、これによって実際に多くの良い効果が得られていると感じています。

まず第一に、コードレビューのコストが減るということです。
特に、コーディング規約違反の指摘というのは機械的で面倒な作業です。
本PRテストでは、機械的に分かる部分に関しては自動的に指摘してくれるため、人間はより本質的で高度なレビューに時間を割くことができます。
また、単純なミスに関しては人間から指摘されるより機械に指摘された方が、修正する側も余計なストレスもなく済むという利点もあります。

別の効果として、マージされたものの実際に手元に落としてきたら動かないというような事態もかなり減らすことができます。
普通は手元で動作確認をしてからプルリクエストを作るはずなので、コンパイルエラーが起こることなんて無いのではないかと思われるかもしれませんが、何らかのコミット漏れやちょっとしたミスによりコンパイルエラーが起きてしまうことは意外とあります。
もしそのような状態のものが開発の本流のブランチにマージされてしまうと、多くのエンジニアの作業を一時的に止めてしまったり、アプリのビルドが滞ってしまったりということになります。
PRテストによって、動作に関してのある程度の担保がされているので、開発をスムーズに進められていると感じます。

# おわりに

本記事では、UnityプロジェクトにおけるPRテストの事例を紹介しました。
Unityプロジェクトにおいて具体的にどのようなPRテストを行えば良さそうかの参考にして頂ければと思います。
また、この記事では紹介されていないもので、このようなテストを行うと効果があるといった情報を頂けると非常に嬉しいので、ぜひコメント等で教えて頂ければと思います。