【Unity】Unity Test Runner(Test Framework)でテストの前後処理を書く方法まとめ

Unity Test Runner(Test Framework)でテストの前後処理を書く方法をまとめました。

Unity 2019.3.5
Test Framework 1.1.11

はじめに

この記事ではUnity Test Frameworkでテストをする際にテストの準備や片付けのための前後処理を書く方法をまとめます。

Unity Test Frameworkの基本的な使い方は以下の記事で紹介していますので、必要に応じて参照してください。

light11.hatenadiary.com

テスト前後の処理を書く

各テストの前後処理 - Setup / TearDown

各テストの前後に処理をするにはSetupアトリビュートTearDownアトリビュートを使用します。

using NUnit.Framework;
using UnityEngine;

namespace ExampleEditorTests
{
    public class ExampleTest
    {
        // このクラスに定義された各テストが実行される前に、テストごとに一回ずつ呼ばれる
        [SetUp]
        public void Setup()
        {
            Debug.Log("Setup");
        }

        // このクラスに定義された各テストの実行終了後に、テストごとに一回ずつ呼ばれる
        [TearDown]
        public void TearDown()
        {
            Debug.Log("TearDown");
        }
    }
}
コルーチンを使った各テストの前後処理 - UnitySetup / UnityTearDown

前節のSetupアトリビュートTearDownアトリビュートNUnitで定義されているものであり、Unityのコルーチンには対応していません。
コルーチンを使って数フレームに渡る前後処理を書きたい場合にはUnitySetupアトリビュートUnityTearDownアトリビュートを使用します。

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

namespace ExampleEditorTests
{
    public class ExampleTest
    {
        // このクラスに定義された各テストが実行される前に、テストごとに一回ずつ呼ばれる
        [UnitySetUp]
        public IEnumerator Setup()
        {
            Debug.Log("Setup");
            yield break;
        }

        // このクラスに定義された各テストの実行終了後に、テストごとに一回ずつ呼ばれる
        [UnityTearDown]
        public IEnumerator TearDown()
        {
            Debug.Log("TearDown");
            yield break;
        }
    }
}
全テストの実行前後に一回だけ処理 - OneTimeSetUp / OneTimeTearDown

全テスト実行前に一回だけ前処理をすればいいときには、OneTimeSetUpアトリビュートを使用します。
同様に後処理にはOneTimeTearDownアトリビュートを使用します。

using NUnit.Framework;
using UnityEngine;

namespace ExampleEditorTests
{
    public class ExampleTest
    {
        // このクラスに定義されたテストのうち最初のテストが実行される前に一回呼ばれる
        [OneTimeSetUp]
        public void Setup()
        {
            Debug.Log("Setup");
        }

        // このクラスに定義されたテストのうち最後のテストが実行された後に一回呼ばれる
        [OneTimeTearDown]
        public void TearDown()
        {
            Debug.Log("TearDown");
        }
    }
}
前後処理を別クラスに切り出す - ITestAction

前後処理を別クラスに切り出して定義する場合にはNUnitAttributeを継承したアトリビュートを定義します。
またそのクラスにITestActionを実装します。

using System.Collections;
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using UnityEngine;
using UnityEngine.TestTools;

namespace ExampleEditorTests
{
    public class ExampleTest
    {
        // カスタムアトリビュートBeforeAfterTestを指定
        [Test, BeforeAfterTest]
        public void ExampleTest01()
        {
            Debug.Log("Test01");
        }
    }

    public class BeforeAfterTestAttribute : NUnitAttribute, ITestAction
    {
        // テスト前処理
        public void BeforeTest(ITest test)
        {
            Debug.Log("Before Test");
            yield break;
        }

        // テスト後処理
        public void AfterTest(ITest test)
        {
            Debug.Log("After Test");
            yield break;
        }

        public ActionTargets Targets => ActionTargets.Test;
    }
}

ActionTargetsはDefaultTestSuiteから選択します。
TestにするとSetupやTearDownアトリビュートと同様にテストごとに実行され、
SuiteにするとOneTimeSetUpやOneTimeTearDownのように全テストの前後に一度実行されるようです。

コルーチンを使った前後処理を別クラスに切り出す - IOuterUnityTestAction

コルーチンを使った前後処理を別クラスに切り出すにはIOuterUnityTestActionを実装します。

using System.Collections;
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using UnityEngine;
using UnityEngine.TestTools;

namespace ExampleEditorTests
{
    public class ExampleTest
    {
        // カスタムアトリビュートBeforeAfterTestを指定
        [Test, BeforeAfterTest]
        public void ExampleTest01()
        {
            Debug.Log("Test01");
        }
    }

    public class BeforeAfterTestAttribute : NUnitAttribute, IOuterUnityTestAction
    {
        // テスト前処理
        public IEnumerator BeforeTest(ITest test)
        {
            Debug.Log("Before Test");
            yield break;
        }

        // テスト後処理
        public IEnumerator AfterTest(ITest test)
        {
            Debug.Log("After Test");
            yield break;
        }
    }
}
実行順について

ここまでで紹介したアトリビュートの実行順は以下の通りとなります。

  1. IOuterUnityTestAction.BeforeTest()
  2. UnitySetUp
  3. SetUp
  4. ITestAction.BeforeTest()
  5. テスト本体
  6. ITestAction.AfterTest()
  7. TearDown
  8. UnityTearDown
  9. IOuterUnityTestAction.AfterTest()

ビルドの前後処理を書く

さてここまでで紹介した方法を使うとテストの前後に処理を行うことができます。
しかし「テスト用のリソースを作成して、テストが終わったら削除する」といった処理はできません。

このような処理にはビルドの前後処理を実装する必要があります。
ビルド前後処理は、エディタでテストを実行する際にはUnityが再生される前 / 再生が終わった後に呼ばれます。
また実機でテストを行う際にはビルドが行われる直前と直後に呼ばれます。

ビルド前後の処理 - IPrebuildSetup / IPostBuildCleanup

それでは実際にビルド前後の処理を書いてみます。
ビルドの前後処理をするにはテスト用のクラスにIPrebuildSetupIPostBuildCleanupを実装します。

using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace ExampleEditorTests
{
    // IPrebuildSetup, IPostBuildCleanupを実装
    public class ExampleTest : IPrebuildSetup, IPostBuildCleanup
    {
        [Test]
        public void ExampleTest01()
        {
            var prefab = Resources.Load<GameObject>("TestPrefab");
            Assert.That(prefab.name == "TestPrefab");
            Assert.That(prefab.GetComponent<MeshFilter>().sharedMesh.name == "Cube");
        }

        // ビルド前処理
        public void Setup()
        {
#if UNITY_EDITOR
            AssetDatabase.CreateFolder("Assets", "Resources");
            var go = GameObject.CreatePrimitive(PrimitiveType.Cube);
            var path = "Assets/Resources/TestPrefab.prefab";
            PrefabUtility.SaveAsPrefabAsset(go, path);
#endif
        }

        // ビルド後処理
        public void Cleanup()
        {
#if UNITY_EDITOR
            AssetDatabase.DeleteAsset("Assets/Resources");
#endif
        }
    }
}

テスト前にResourcesフォルダを作ってその中にPrefabを生成しています。
ビルド後にはResoourcesフォルダを削除しています。

ビルド前後処理はエディタで行われるため、UnityEditorの機能が使えますが、
UNITY_EDITORで囲まないとビルド時にコンパイルエラーになるので注意してください。

ビルド前後の処理を別クラスに切り出してテストごとに指定

ビルドの前後処理を別クラスに切り出すこともできます。
この場合にはIPrebuildSetupIPostBuildCleanupを実装したクラスを定義し、
この型を当該テストのPrebuildSetupアトリビュートPostBuildCleanupアトリビュートに渡します。

using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace ExampleEditorTests
{
    public class ExampleTest
    {
        // PrebuildSetup、PostBuildCleanupアトリビュートでクラスを指定
        [Test, PrebuildSetup(typeof(PrePostBuildProcess)), PostBuildCleanup(typeof(PrePostBuildProcess))]
        public void ExampleTest01()
        {
            Debug.Log("Test01");
        }
        
        [Test]
        public void ExampleTest02()
        {
            Debug.Log("Test02");
        }
    }

    public class PrePostBuildProcess : IPrebuildSetup, IPostBuildCleanup
    {
        // ビルド前処理
        // 定義されている全てのIPrebuildSetupのうち一つでも例外を吐いたらCleanup処理が呼ばれないので注意
        public void Setup()
        {
#if UNITY_EDITOR
            AssetDatabase.CreateFolder("Assets", "Resources");
            var go = GameObject.CreatePrimitive(PrimitiveType.Cube);
            var path = "Assets/Resources/TestPrefab.prefab";
            PrefabUtility.SaveAsPrefabAsset(go, path);
#endif
        }

        // ビルド後処理
        public void Cleanup()
        {
#if UNITY_EDITOR
            AssetDatabase.DeleteAsset("Assets/Resources");
#endif
        }
    }
}

ちなみにこのアトリビュートはクラスにつけることもできます。

テスト用にビルド設定を変更する

ITestPlayerBuildModifierを使えばビルド設定をテスト用に変えることができます。
これはPrebuildSetupなどとは少し違い、Run all in playerなどPlayerをビルドする時のみ適用されます。

using UnityEditor;
using UnityEditor.TestTools;

[assembly:TestPlayerBuildModifier(typeof(BuildModifier))]
public class BuildModifier : ITestPlayerBuildModifier
{
    public BuildPlayerOptions ModifyOptions(BuildPlayerOptions playerOptions)
    {
        if (playerOptions.target == BuildTarget.Android)
        {
            playerOptions.options |= BuildOptions.Development;
            playerOptions.options |= BuildOptions.AllowDebugging;
        }

        return playerOptions;
    }
}

関連

light11.hatenadiary.com

参考

docs.unity3d.com

nunit.org