C#
NUnit
Unity
UnityTestRunner

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

今すぐ使え!

Unityを使っている人は

  • 拾ってきたサンプルコードの挙動を確認したい
  • 「書いたはいいが自信がないコード」の実際の動きを確認したい
  • UniRx とか Zenject とか難しいライブラリ/フレームワークを使ったコードを書いていろいろ実験したい

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

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

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

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

今すぐ使え!

なんで?

csharppad ではUnityやプロジェクトのコンテキストでコードを実行できないので論外として、それ以外の確認方法についての話。

  1. コードを書く
  2. 再生ボタンを押してプレイモードに入る
  3. コードの挙動を確認する
  4. 再生ボタンを押してプレイモードを終了する
  5. コードを修正し2.へ(以下ループ)

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

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

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

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

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

Unity Test Runnerってなに?

Unityのテスト実行ツールで、NUnit という.NET用テスティングフレームワークが使われている。

TestRunner001.png
グローバルメニュー→「Window」→「Test Runner」でウィンドウを開く

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

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

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

TestRunner002.png
「Run All」「Run Selected」などのボタンがあり、使い方は雰囲気でわかる

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

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

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

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

2種類のテスト

EditModeテスト,PlayModeテスト共通

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

の2つがあるが、基本的にはUnityのものを使う[3]

Assertチートシート

UnityEngine.Asertions.Assertのメソッドをまとめた。
ちなみに引数を2つ取る場合は(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

※詳細はリファレンスを参照
※NUnitのものと違い、UnityのAssertにはThat()などはない(貧弱ゥ!)

EditModeテスト

  • テストコードを記述したクラスファイルはEditor/以下に置く必要がある
  • プレイモードを経由せず、エディタ上で即実行される
    • プレイモード移行のオーバーヘッドがなく、爆速で実行できる
    • 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/以下ではない場所に置く必要がある
  • テストコードは自動生成されたテスト用のシーン上で実行される(通常のプレイモードと同じくらい時間がかかる
    • MonoBehaviourのテストが可能
    • 1. テスト用シーンファイルが自動生成される
    • 2. プレイモードへ移行
    • 3. テストコード実行(複数のテストを実行する場合はすべて実行)
    • 4. プレイモード終了
    • 5. テスト用シーンが自動削除される
  • [UnityTest]yield returnが特に制限なく使える。非同期テストしたい場合はこっち。
  • 個々のテスト実行後の破棄処理を適切に行わないと他のテストに影響が出る可能性がある
    • 1テストごとに呼ばれる[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.(マニュアルより)

あとがき

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

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

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

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

NUnitを知らなかった人でもテストを書けるように続きも書いた。Unity Test Runnerの制限についても触れている。
 
 続き→ Unityでちゃんとテストを書きたい人のためのまとめ

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

参考リンク

あとで書く
 
  


  1. コンパイル時間をなるべく短くする方法もある。 テラシュールブログ の記事参照。 

  2. 他にも[TestCase],[TestCaseSource)]などがあり、いずれも同期的に実行される。 

  3. Unity向けに最適化されていて、本番ビルド時にストリッピングされる。詳細は Unity公式ブログ の記事を参照。