vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

PlayModeでUnity Test Toolsを使ってみた

はじめに

Unityでテストを書くのに使用するUnity Test Tools。
Unity5.3から組み込みとなり、AssetStoreで別途インストールする必要はなくなりました。

ただ、CoroutineやTween系(iTweenやDOTweenとか)は確認することができず、別の方法でテストを行う必要がありました。

が、5.6から強化され、PlayModeでテストを行うことができるようになりました。

ということで今回は、PlayModeでDOTweenを使って3Dモデルを移動させるコードをテストしてみることにしました。

準備

前述の通り、Unity Test Toolsを使うのに何かをインストールする必要はありません。

ただし、デフォルトではPlayModeでのテスト実行が無効になっているため、
これを有効にします。

  1. メニューの Window > Test Runner からTest Runnerウインドウを開く
  2. PlayMode タブを開き、 Enable playmode tests をクリックして有効にする
  3. UnityEditorを再起動する

f:id:mslGt:20170527010432j:plain

あとは PlayMode タブの Create PlayMode test または右クリック > Create > Testing > PlayMode Test C# Script からテスト用クラスを追加します。
(※PlayMode用のファイルは、Editorフォルダ内に入れてしまうとPlayMode用のTest Scriptと認識されないようなので、
それ以外の場所に保存する必要があります
)

テスト対象のコード

テスト対象のコードは、下記の関数 Move とします。

ObjectController.cs

using DG.Tweening;
using UnityEngine;

public class ObjectController : MonoBehaviour
{
    public GameObject EventHandleObject;
    protected ObjectCtrlEventHandler ObjectEventHandler;
    
    public Tweener Move(GameObject targetObject, Vector3 goalPosition, float duration, Ease easeType)
    {
        return targetObject.transform.DOMove(goalPosition, duration)
            .SetEase(easeType)
            .OnComplete(ObjectEventHandler.OnFinished);
    }
    private void Awake()
    {
        ObjectEventHandler = EventHandleObject.GetComponent();
    }
}

ObjectCtrlEventHandler.cs

using UnityEngine;

public class ObjectCtrlEventHandler : MonoBehaviour
{
    public void OnFinished()
    {
        Debug.Log("Finished");
    }
}
  • 引数として渡しているGameObject(targetObject)を、goalPositionの位置までduration秒で移動させる、という内容です。
  • 移動が完了したら ObjectCtrlEventHandler > OnFinished が呼ばれます。

テストを書く

この関数に対するテストを書きます。

ObjectControllerTest.cs

using System.Collections;
using DG.Tweening;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;

namespace Tests
{
    public class ObjectControllerTest: ObjectController
    {
        private GameObject targetObject;
        
        [SetUp]
        public void Init()
        {
            // 初期化処理. JUnitでいう@Before.
            EventHandleObject = new GameObject();
            ObjectEventHandler = EventHandleObject.AddComponent();
            
            targetObject = new GameObject();
        }
        [Test]
        public void ObjectControllerTestSimplePasses()
        {
            // 待ち時間が不要な処理はTestを使う.
        }
        [UnityTest]
        public IEnumerator MoveIn0Sec()
        {
            var tween = Move(targetObject, Vector3.one, 0f, Ease.Flash);
            // Tweenerを返す場合OnCompleteの処理をダミーに置き換えることができる.
            tween.OnComplete(() => Debug.Log(""));
            
            // 結果が戻るのが次フレーム以降のため、少し待つ.
            yield return new WaitForSeconds(0.1f);
            
            Assert.AreEqual(targetObject.transform.position, Vector3.one);
        }

        [TearDown]
        public void Dispose()
        {
            // 終了処理. JUnitでいう@After.
        }
    }
}

Testの実行

上記のようなTest Scriptを作成すると、Test Runnerウィンドウ > PlayModeタブ に該当のTestが表示されます。
あとは「Run All」や「Run Selected」をクリックすればテストが実行されます。

NUnit

  • テストコードは、NUnit(ver.2.6.4)がベースになっており、初期化処理([SetUp])、終了処理([TearDown])が使用できます。

初期化処理

  • テスト対象である ObjectController のAwakeでGetComponentしている ObjectCtrlEventHandler は、
    Testでは(GameObjectにアタッチができないため)NullReferenceExceptionになるので SetUp で値をセットしています。

DOTweenの処理が反映されるタイミング

  • DOTweenで処理を行う場合、durationを0にしていても同フレーム内では座標値の変更が反映されないため、 WaitForSeconds で待ち時間を設けます。
  • [Test] では戻り値がvoidに限定されるため、 [UnityTest] を使って戻り値を IEnumerator にし、yield return new WaitForSeconds を使用しています。

f:id:mslGt:20170527010612j:plain

おわりに

これまで以上にテスト可能な箇所が増えたことで、ぐんとテストが書きやすくなったように思います。

一点気になっているのは、例えばJenkinsなどのCIツールを利用する場合にも、今回のテストは実行できるのか?ということです。

こちらについては近いうちに試してみたいと思います。

参照