Unity Test Runner(Test Framework)でテストの前後処理を書く方法をまとめました。
Unity 2019.3.5
Test Framework 1.1.11
はじめに
この記事ではUnity Test Frameworkでテストをする際にテストの準備や片付けのための前後処理を書く方法をまとめます。
Unity Test Frameworkの基本的な使い方は以下の記事で紹介していますので、必要に応じて参照してください。
テスト前後の処理を書く
各テストの前後処理 - 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はDefault
かTest
、Suite
から選択します。
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; } } }
実行順について
ここまでで紹介したアトリビュートの実行順は以下の通りとなります。
- IOuterUnityTestAction.BeforeTest()
- UnitySetUp
- SetUp
- ITestAction.BeforeTest()
- テスト本体
- ITestAction.AfterTest()
- TearDown
- UnityTearDown
- IOuterUnityTestAction.AfterTest()
ビルドの前後処理を書く
さてここまでで紹介した方法を使うとテストの前後に処理を行うことができます。
しかし「テスト用のリソースを作成して、テストが終わったら削除する」といった処理はできません。
このような処理にはビルドの前後処理を実装する必要があります。
ビルド前後処理は、エディタでテストを実行する際にはUnityが再生される前 / 再生が終わった後に呼ばれます。
また実機でテストを行う際にはビルドが行われる直前と直後に呼ばれます。
ビルド前後の処理 - IPrebuildSetup / IPostBuildCleanup
それでは実際にビルド前後の処理を書いてみます。
ビルドの前後処理をするにはテスト用のクラスにIPrebuildSetup
やIPostBuildCleanup
を実装します。
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で囲まないとビルド時にコンパイルエラーになるので注意してください。
ビルド前後の処理を別クラスに切り出してテストごとに指定
ビルドの前後処理を別クラスに切り出すこともできます。
この場合にはIPrebuildSetup
やIPostBuildCleanup
を実装したクラスを定義し、
この型を当該テストの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; } }