【unity】Test Runnerを使ってみる

初書 ： 2021/06/01
mac : 11.4
unity : 2020.3.10f1
vscode : 1.56.2

# 前置き

今回はunityでもテストコードを書いておきたい！ということで調べていたらTest Runnerなるものが存在しているらしいので、それを使ってみる。


# Test Runnerとは

> Unity Test Runner は、Edit (編集) モードと Play (再生) モードでコードをテストするツールです。また、スタンドアロン、Android、iOS などのターゲットプラットフォーム上でも同様にテストできます。

[Unity Test Runner - Unity マニュアル](https://docs.unity3d.com/ja/2018.4/Manual/testing-editortestsrunner.html)より

つまり、Unityに標準でついているテストツール。なるほど、使うしかない。

# 起動 (？)

果たして起動と表現するものなのか。
とりあえずウィンドウを表示してみる。

`ウィンドウ(window)`→`一般(general)`→`Test Runner`から開くと、上に`PlayMode | EditMode`と書かれたウィンドウが開く。

テストは主にこの画面から行うらしい。

# テストコードを書く準備をする

テストコードは単純に`.cs`を置くだけではできないので、準備をする必要がある。

ディレクトリはどこでもいいのだが、`PlayMode`と`EditMode`のテストコードと、実際にunityで使うメインのコードはそれぞれ別のディレクトリに入れる必要があるため、ここは簡単に両方のフォルダを作ってしまう。

まず、`Assets`直下に`Tests\Editor`ディレクトリを作成する。

その後、右クリックから`作成(create)`→`Testing`→`Tests Assembly Folder`を選択し、`EditModeTests`と`PlayModeTests`の２つのディレクトリをそれぞれ作成する。

２つ作成できたら、`EditModeTests`の中に入り、`EditModeTests.asmdef`[^1]を開く（インスペクターに表示）

[^1]: 名前が同じでややこしいので拡張子を記述したが、UnityEditorでは拡張機能を入れない限り拡張子は見えない。

その後、`プラットフォーム`の箇所を`Editor`にのみチェックが入っている状態にする。（任意のプラットフォームのチェックを外し、全ての選択を解除し、Editorにチェック）

これにより、`EditModeTests`下に入れたテストスクリプトは、`EditMode`で動作し、`PlayModeTests`下に入れたスクリプトは`PlayMode`で動作する。

# PlayMode, EditModeとは

そういえば説明をしていなかった。詳しくどこかに書いているのかは知らないが、

PlayMode : テストコードに沿ってゲームを再生し、テストを行う
EditMode : ゲームの再生を行うことなく、テストコードを実行する

大体２つの違いはこんな感じ。つまり、関数の動作を確認したい時などは`EditMode`を使い、わざわざゲームを実行することなくスクリプトをテストできる。
逆に(3D)オブジェクトの動作を確認したい時などは`PlayMode`を使い、ゲームを実行しながらテストを行う感じ。

ちなみにこれ記述時点では`EditMode`しか使っていないため、PlayModeの詳しいお話は後日追記する予定。

# (EditMode)テストコードを書く…前準備(2回目)

テストコードを書く前に、テスト対象となるコードがなければ話にならないので、まずはそちらを作成する。

`Assets/Scripts/TestedCode.cs`を作成し、適当にコードを記述する。

```C#:TestedCode.cs
using UnityEngine;

public class TestedCode{
    /// <summary>
    /// 料金に税金を上乗せします。
    /// </summary>
    /// <param name="Fee">料金</param>
    /// <param name="isFood">食料品かどうか</param>
    /// <returns>税金が入った金額</returns>
    public static int AddTax(int Fee, bool isFood){
        if(isFood){
            return MultiplyAndRoundup(Fee, 1.08f);
        }else{
            return MultiplyAndRoundup(Fee, 1.1f);
        }
    }

    /// <summary>
    /// ２数を掛け算し、小数点以下を切り上げ整数にします。
    /// </summary>
    /// <param name="number">数値</param>
    /// <param name="magnification">倍率</param>
    /// <returns>切り上げた値</returns>
    private static int MultiplyAndRoundup(int number, float magnification){
        return Mathf.CeilToInt(number * magnification);
    }
}
```

適当なわりに若干複雑なことしているのは後で使うからです。（そしてこれくらいのコードしか思いつかなかった）

次にテストコードを書きたいのだが、実はこのままだと記述できない。

# Assembly Definition

unityにはもう一つ大切なものとして、Assembly Definitionというのがある。これの詳しい説明は他の方に任せるとして、必要な部分だけ説明する。

[Unity Assembly Definition 完全に理解した - Qiita](https://qiita.com/toRisouP/items/d206af3029c7d80326ed)

簡単にいうと、複数の`.cs`をまとめて一つのアセンブリにする。そのアセンブリを複数に分割するのがAssembly Definition。

で、複数に分割するキーになるものとして、先程の`EditModeTests.asmdef`や`PlayModeTests.asmdef`が該当する。

このファイルを含んだディレクトリとその下の階層は全て一括りにされて、一つのアセンブリとなる。

・・・説明が下手なので今の階層構造を持ってくると、

```
├── Scripts
│   ├── TestedCode.cs
└── Tests
    └── Editor
        ├── EditModeTests
        │   └── EditModeTests.asmdef
        └── PlayModeTests
            └── PlayModeTests.asmdef
```

`EditModeTests.asmdef`がある`EditModeTests`ディレクトリと、その下（今はない）が`EditModeTests.dll`というアセンブリにまとめられ、
`PlayModeTests.asmdef`がある`PlayModeTests`ディレクトリと、その下（今はない）が`PlayModeTests.dll`というアセンブリにまとめられる。

そしてどちらにも入っていない`Scripts`とその下や`Tests\Editor`のところは、その他として`Assembly-CSharp`というアセンブリにまとめられる。

で、アセンブリ同士では基本的に変数やクラスのやりとりが出来ず、アクセスを求める際は手動で接続する必要がある。

ただし、`Assembly-CSharp`というのは特別で、他のどのアセンブリからも参照することは出来ない。（手動でもできない）

つまり、今からテストする`Assets/Scripts/TestedCode.cs`は`Assembly-CSharp`にあるため、`EditModeTests`アセンブリからアクセスすることが出来ないのである。

（説明難しいな・・・伝わるのかな・・・）

ということでアクセスする方法としては、`TestedCode.cs`を含むスクリプトをアセンブリ化するしかないので、それを作成する。

ここまでの説明で理解できて、応用する場合は好きな形でアセンブリを置いてもらえればいいのだが、今回はとりあえず`Assets`直下に置くことにする。

右クリック→`作成`→`アセンブリ定義`で、今回は`MainScript`と命名する。

その後、`手動で接続する`ために、`EditModeTests.asmdef`を開き、`アセンブリ定義参照`のところに先程の`MainScript`を追加する。

これでようやくテストコードを記述することができる。

# テストコードを書く

`EditModeTests`ディレクトリにいき、右クリック→`作成`→`Testing`→`C# Test Script`で`TestCode`を作成

```C#:TestCode.cs
using NUnit.Framework;

public class TestCode{
    [Test]
    public void TestCodeAddTax(){
        int fee = TestedCode.AddTax(1000,false);
        Assert.AreEqual(1100, fee);
    }
}
```

`[Test]`と書かれた関数がテスト対象で、正誤判定は`Assert.AreEqual`で行っている。
左側に期待値、右側に実測値を入れ、不正解だとエラーが起きる。

これで保存し、`Test Runner`の画面を開く。

`EditMode`を選択したら、`Run All`を選択。

これでテストが行われ、テストが通ればチェックマーク、通らなければ下にエラーが表示される。

これで無事テストが終了した。

# private関数のテストコードを書く

TestedCode.csにはもう一つ`MultiplyAndRoundup`という関数がある。せっかくなのでこちらもテストをしておく。

先ほどと同様にテストをやれればいいのだが、こちらの関数はprivate関数になっていて、そのままではテストできない。

ということで今回は`MethodInfo.Invoke`というのを使用して、private関数をテストしてみる。

```C#:TestCode.cs
using System.Reflection; // これだけ先頭に追加。他はTestCodeクラス内

    [Test]
    public void TestCodeMultiplyAndRoundup(){
        System.Type myClass = typeof(TestedCode);
        Assert.NotNull(myClass);
        MethodInfo myMethod = myClass.GetMethod("MultiplyAndRoundup", BindingFlags.NonPublic | BindingFlags.Static);
        Assert.NotNull(myMethod);
        var value = (int) myMethod.Invoke(this, new object[]{1000,1.1f});
        Assert.AreEqual(1100, value);
    }
```

簡単に解説すると、myClassに型情報を入れ、GetMethod関数を使って`NonPublic`で`Static`な`MultiplyAndRoundup`を取り出し、
`Invoke`を使って引数に`1000,1.1f`を入れて呼び出し、valueに戻り値を入れている。
戻り値はそのまま取得するとオブジェクト型になってしまうので、int型にキャスとしている。

このような形で取得することができる。

あとは先ほどと同様テストするだけ。

# 終わりに

`PlayMode`のテストは後日使ったら追記する。
まだ使いたてなのでもしかしたら間違えているところや、より最適な方法があるかもしれないが、もしそういうのがあればコメントで教えてください。


# 参考サイト：

[Unityでテストを書くのが当然になる時代に今から備えよう - Qiita](https://qiita.com/naninunenoy/items/b5092774fed30739adbc)

[Unity のUnitTestでprivate な関数をテストしたい場合 | おなかソフト](https://onaca.jp/blog/286)

# その他役立ちそうなサイト：

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

[[C#]private、internalなメソッドのUnitTest - Qiita](https://qiita.com/tera1707/items/c1d4dee3611bb693dd10)