Unity使いは全員Unity Test Runnerを使え！爆速のトライ＆エラー環境だぞ！

<font color=red>##### 2020/7/1追記 #####</font>

この記事は古いので最近書かれたブログ記事を紹介しておきます。

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

↓の記事はテストの書き方をいろいろ紹介してるのでまだ参考になるかも？

* [Unityでちゃんとテストを書きたい人のためのまとめ](https://qiita.com/su10/items/560302ceafa6bf43a13e)

<font color=red>##### 追記ここまで #####</font>

# 今すぐ使え！
Unityを使っている人は

* 拾ってきたサンプルコードの挙動を確認したい
* 「書いたはいいが自信がないコード」の実際の動きを確認したい
* [UniRx](https://github.com/neuecc/UniRx) とか [Zenject](https://github.com/modesttree/Zenject) とか難しいライブラリ/フレームワークを使ったコードを書いていろいろ実験したい

と思ったときに、いったいどうしているだろうか？

* 既存プロジェクトの適当な`MonoBehaivour`継承クラスに間借りしてコードを書いてプレイモードで確認？
* `[RuntimeInitializeOnLoadMethod]`をつけたメソッドを書いてプレイモード開始と同時に実行して確認？
* [csharppad](https://csharppad.com/) で実行して確認？

まったくもってナンセンス。時間のムダ！

**Unity Test Runnerを使え！「テストコードとかめんどくさくて書く気ないし」とか関係ない！**

<font color="red">**今すぐ使え！**</font>

# なんで？
[csharppad](https://csharppad.com/) ではUnityやプロジェクトのコンテキストでコードを実行できないので論外として、それ以外の確認方法についての話。

0. コードを書く
0. **再生ボタンを押してプレイモードに入る**
0. コードの挙動を確認する
0. **再生ボタンを押してプレイモードを終了する**
0. コードを修正し2.へ（以下ループ）

このサイクルにおけるそれぞれのステップにかかる時間的コストはかなり大きい。

コンパイルに時間がかかるのはしょうがない<sup>[</sup>[^1]<sup>]</sup>としても、毎度毎度プレイモードに移行する2,4のオーバーヘッドがひどい。

また、既存のコード/コンテキストに影響を受けて、それ単体で得られるはずだった結果とは異なる結果になるかもしれない。

　
　
Unity Test Runnerを使うと実行したいコードのコンテキストを局所化でき、**プレイモード移行のオーバーヘッドを0にしてトライ＆エラーを爆速にできる**可能性がある。

（実行にかかった時間も自動で計測・表示されるので、簡易的なパフォーマンス計測にも利用できる）

# Unity Test Runnerってなに？
Unityのテスト実行ツールで、[NUnit](https://github.com/nunit/docs/wiki/NUnit-Documentation) という.NET用テスティングフレームワークが使われている。

![TestRunner001.png](https://qiita-image-store.s3.amazonaws.com/0/27863/ae423c98-bb3b-444c-d113-87841b2ea6e3.png)
*グローバルメニュー→「Window」→「Test Runner」でウィンドウを開く*

テストコードを書くとウィンドウに一覧で表示してくれて、

* 全部実行
* 選択して実行
* 特定のカテゴリのテストだけ実行
* 選択したプラットフォーム向けにビルドしたプレイヤー上で実行

などができる。ボタンワンクリック（もしくは項目をダブルクリック）で実行可能。

![TestRunner002.png](https://qiita-image-store.s3.amazonaws.com/0/27863/1e08cb6f-2763-83ef-d693-e92edda0ad64.png)
*「Run All」「Run Selected」などのボタンがあり、使い方は雰囲気でわかる*

使い方に関する詳細は以下を参考に

* ~~[日本語公式マニュアル](https://docs.unity3d.com/jp/530/Manual/testing-editortestsrunner.html)~~（5.3なので古い）
* [英語公式マニュアル](https://docs.unity3d.com/ja/current/Manual/testing-editortestsrunner.html)（おそらく最新）

最初はテストの機能を有効にするために若干のクリック操作が必要、マニュアル読むべし。

設定が済んだら後はクラスファイルを新しく作ってテストコードを書くだけ。とりあえず後述のサンプルクラスをそのままコピペして実行してみて、雰囲気を掴むといい。ぜんぜん難しくない。

　
　
テストは実行する環境によって**EditModeテスト**と**PlayModeテスト**の２種類がある。

#２種類のテスト
## EditModeテスト,PlayModeテスト共通
* 属性`Attrubute`は基本的にNUnitのものを利用する
* テストコードとして一覧表示/実行したいメソッドに`[Test]`もしくは`[UnityTest]`をつける<sup>[</sup>[^2]<sup>]</sup>
  * `[Test]`は同期的に実行される
     * 戻り値の型は必ず`void`
  * `[UnityTest]`はコルーチンとして実行される
     * 戻り値の型は必ず`IEnumerator`
     * メソッド内で`yield return`が使える（モードによって一部制限あり）
  * `[Test]`,`[UnityTest]`などを含むクラスファイルが`Editor/`以下にあるならEditModeテスト、そうでなければPlayModeテストと認識される
* テストコードを専用の別クラスに分けてもいいし、テストしたいクラス内に直接書くこともできる
* NUnitと違ってテストコードを記述したクラスに`[TestFixture]`をつける必要はない

```c#
using System.Collections;
using NUnit.Framework;
using UnityEngine.TestTools;
using Assert = UnityEngine.Assertions.Assert; // AssertはNUnitのではなくUnityのものを使う

public class NewEditModeTest
{
    [Test]
    public void SimplePasses()
    {
        Assert.AreEqual(2, 1 + 1);
    }

    [UnityTest]
    public IEnumerator EnumeratorPasses()
    {
        yield return null;
        Assert.AreEqual(2, 1 + 1);
    }
}
```

コードの挙動を確認したいだけの場合は`Assert`を使わなくても`Debug.Log()`やブレークポイントによるステップ実行でも十分。

ただ、`Assert`を使うと

* 期待した値でなかったときに表示がわかりやすい
* 失敗したテストだけすべて実行できる

などいくつかメリットがあるので、できれば`Assert`を使う。
初回で何事もなくテストが通ると若干不安になるが、たぶん大丈夫だ。

Unityで使える`Assert`は

* `UnityEngine.Assertions.Assert`
* `NUnit.Framework.Assert`

の２つがあるが、基本的にはUnityのものを使う<sup>[</sup>[^3]<sup>]</sup>。

### `Assert`チートシート
`UnityEngine.Asertions.Assert`のメソッドをまとめた。
ちなみに引数を２つ取る場合は`(expected, actual)`の順番。

| メソッド                       | テスト内容          |
|:------------------------------|:------------------:|
| AreEqual(a,b)                 | a == b かどうか     |
| AreNotEqual(a,b)              | a != bかどうか       |
| IsTrue(a)                     | a == true かどうか  |
| IsFalse(a)                    | a == false かどうか |
| IsNull(a)                     | a == null かどうか  |
| IsNotNull()                   | a != null かどうか  |
| AreApproximatelyEqual(a,b)    | a ≒ b（誤差0.00001f以内）かどうか |
| AreNotApproximatelyEqual(a,b) | ↑のNOT              |

※詳細は[リファレンス](https://docs.unity3d.com/ja/current/ScriptReference/Assertions.Assert.html)を参照
※NUnitのものと違い、Unityの`Assert`には`That()`などはない（貧弱ゥ！）


## EditModeテスト
* テストコードを記述したクラスファイルは`Editor/`以下に置く必要がある
* プレイモードを経由せず、エディタ上で即実行される
  * プレイモード移行のオーバーヘッドがなく、<font color="red"><b>爆速で実行できる</b></font>
  * `MonoBehaviour`のテストは実質不可（`Start()`,`Update()`などが呼ばれない）
* `[UnityTest]`について
  * `yield return`には`null`しか渡せないので非同期のテストは実行不可。エディタ拡張のテスト用？
  * EditorApplication.updateのコールバックループで実行される

```Assets/Tests/EditMode/Editor/ZenjectTest.cs
using NUnit.Framework;
using Zenject;
using Assert = UnityEngine.Assertions.Assert;

// Zenjectの挙動確認を行う（あらかじめZenjectの導入が必要）
public class ZenjectTest
{
    [Test]
    public void ResolveTest()
    {
        var container = new DiContainer();
        container.BindInstance("hoge");

        Assert.AreEqual("hoge", container.Resolve<string>());
    }

    [Test]
    public void ResolveAllTest()
    {
        var container = new DiContainer();
        container.BindInstance("hoge");
        container.BindInstance("fuga");

        var all = container.ResolveAll<string>();

        Assert.AreEqual(2, all.Count);
        Assert.AreEqual("hoge", all[0]);
        Assert.AreEqual("fuga", all[1]);
    }
}
```

EditModeテストは爆速で回せるので基本的にこっちを使えるように工夫する。

例えば Unity API に関係ないロジックは`MonoBehaviour`継承クラスに直接記述するのではなくC#のピュアなクラス内に実装することでEditModeテストを可能にしつつ、必要があれば`MonoBehaviour`継承クラスにインスタンスとして持たせるようにする。

また、

* UniRxの各オペレータ/ストリームの挙動
* ZenjectのピュアなC#用機能の挙動

など、ライブラリ/フレームワークの「Unityのヒエラルキーに依存しない機能」の学習/確認にはEditModeテストが最適。

その際、確認した挙動にはコメントを追記してサンプルコードとして残すことによって、プロジェクトの他のメンバーの学習の手助けとすることができる。

テストコードだと最小構成で実行できるうえにわからない/気になる部分を編集して即実行して試せるのが最高にイケてる。

## PlayModeテスト
* テストコードを記述したクラスファイルは`Editor/`以下<b>ではない</b>場所に置く必要がある
* テストコードは自動生成されたテスト用のシーン上で実行される（**通常のプレイモードと同じくらい時間がかかる**）
  * `MonoBehaviour`のテストが可能
  * 1. テスト用シーンファイルが自動生成される
  * 2. プレイモードへ移行
  * 3. テストコード実行（複数のテストを実行する場合はすべて実行）
  * 4. プレイモード終了
  * 5. テスト用シーンが自動削除される
* `[UnityTest]`で`yield return`が特に制限なく使える。**非同期テストしたい場合**はこっち。
* 個々のテスト実行後の破棄処理を適切に行わないと他のテストに影響が出る可能性がある
  * １テストごとに呼ばれる`[SetUp]`（テスト前）,`[TearDown]`（テスト後）で適切に破棄処理を行う
* **ビルドしたプレイヤー上で実行可能**（すごそう、試してない😅）

```Assets/Scripts/HogeComponent.cs
using UnityEngine;

// 適当なコンポーネントを定義
public class HogeComponent : MonoBehaviour
{
    public int count { get; private set; }

    void Start()
    {
        this.count = 1;
    }
}
```
```Assets/Tests/PlayMode/HogeComponentTest.cs
using System.Collections;
using UnityEngine;
using UnityEngine.TestTools;
using Assert = UnityEngine.Assertions.Assert;

// テスト用クラス
public class HogeComponentTest
{
    [UnityTest]
    public IEnumerator CountTest()
    {
        var go = new GameObject("Hoge");
        var hoge = go.AddComponent<HogeComponent>();

        // Start()前
        Assert.AreEqual(0, hoge.count);

        yield return null;

        // Start()後
        Assert.AreEqual(1, hoge.count);
    }
}
```

EditModeと比べて実行に時間がかかるが、非同期テスト/`MonoBehaviour`関連テストはPlayModeテストとして書く必要がある。

それとPlayModeテストを有効にするとその分ビルド時にバイナリサイズが大きくなるので注意。
> Note: Enabling PlayMode tests for all assemblies includes additional assemblies in your Project’s build, which can increase your Project’s size as well as build time.（[マニュアル](https://docs.unity3d.com/ja/current/Manual/testing-editortestsrunner.html)より）


# あとがき
アプリケーションのテストコードを書くか書かないかはプロジェクトによりけりだが、Unityで「ちょっとコードを書いて実行する」のにこれほど適しているものは他にないと思う。

準備についてもウィンドウを開いて最初の一回だけボタンをポチっとすれば、あとは動かそうと思っていたコードをちょろっと書くだけ。

繰り返しになるがUniRxやZenjectなどの学習にはマストになると思う。

[Reactive Extentions再入門](https://blog.okazuki.jp/entry/20120305/1329923070)（かずきのブログ） を読んだだけでRxをわかったつもりにならず、めんどくさがらずにサンプルコードを自分で実行するとより理解が深まるはずだ。

NUnitを知らなかった人でもテストを書けるように続きも書いた。Unity Test Runnerの制限についても触れている。
　
　続き→ [Unityでちゃんとテストを書きたい人のためのまとめ](https://qiita.com/su10/items/560302ceafa6bf43a13e)


　
　
はてぶとかPocketに突っ込んで忘れようとしている画面の前の愚かなUnity使いは猛省し、今すぐUnityを開いたほうがいい。まじで。

# 参考リンク
あとで書く
　
　　
[^1]: コンパイル時間をなるべく短くする方法もある。 [テラシュールブログ](http://tsubakit1.hateblo.jp/entry/2016/07/25/235815) の記事参照。
[^2]: 他にも`[TestCase]`,`[TestCaseSource)]`などがあり、いずれも同期的に実行される。
[^3]: Unity向けに最適化されていて、本番ビルド時にストリッピングされる。詳細は [Unity公式ブログ](https://blogs.unity3d.com/jp/2015/08/25/the-unity-assertion-library/) の記事を参照。