【Unity】Unity Test Runner(Test Framework)で例外やエラーログが出力されることをテストする方法まとめ

Unity Test Runnerで例外やエラーログが出力されることをテストする方法をまとめました。

Unity 2019.3.5
Test Framework 1.1.11

はじめに

UnityのTest Runnerでは、テストメソッド内で例外が発生したりDebug.LogErrorが実行されるとそのテストは失敗したものとしてみなされます。
しかしこれでは「例外が正しく発生すること」を確認するテストや「正しくエラーログが出力されること」を確認するテストを書きたいときに困ります。

本記事ではこのように例外やエラーログの出力をテストする方法をまとめます。

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

light11.hatenadiary.com

例外をテストする

それではまず例外をテストする方法をまとめます。

例外が出力されることをテストする

例外が狙い通りに出力されることを確認するためには以下のようにThrowsコンストレイントを使います。

using System;
using NUnit.Framework;

namespace ExampleTests
{
    public class ExampleTest
    {
        [Test]
        public void ExampleTest01()
        {
            void TestMethod()
            {
                throw new InvalidOperationException();
            }
            // InvalidOperationExceptionがスローされたらテスト成功となる
            Assert.That(TestMethod, Throws.TypeOf<InvalidOperationException>());
        }
    }
}

上記のように書くと、テスト対象のコード内でInvalidOperationExceptionがスローされたらテスト成功となります。
例外がスローされなかったり、他の例外がスローされたらテスト失敗です。

ちなみに以下のようにAssert.Throws()を使うこともできます。

Assert.Throws<InvalidOperationException>(TestMethod);
例外の派生クラスも許容する

さて前節方法では指定した例外の型とスローされる型が完全に一致する場合のみテスト成功となります。
もし派生した型も許容したい場合には以下のようにInstanceOfを使う形に書き換えます。

using System;
using NUnit.Framework;

namespace ExampleTests
{
    public class ExampleTest
    {
        [Test]
        public void ExampleTest01()
        {
            void TestMethod()
            {
                throw new InvalidOperationException();
            }
            // Exceptionの派生クラスがスローされたらテスト成功
            Assert.That(TestMethod, Throws.InstanceOf<Exception>());
        }
    }
}

上記の例ではテスト対象のメソッドからExceptionの派生クラスがスローされたらテスト成功となります。

ちなみにAssert.Catchを使って以下のように書くこともできます。

Assert.Catch<Exception>(TestMethod);
例外のメッセージをテストする

例外が発生した際に特定のメッセージが設定されていることをテストしたい場合には以下のようにします。

using System;
using NUnit.Framework;

namespace ExampleTests
{
    public class ExampleTest
    {
        [Test]
        public void ExampleTest01()
        {
            void TestMethod()
            {
                throw new InvalidOperationException("test");
            }
            // InvalidOperationExceptionがスローされてメッセージがtestだったらテスト成功となる
            Assert.That(TestMethod, Throws.TypeOf<InvalidOperationException>().With.Message.EqualTo("test"));
        }
    }
}

上記の例ではInvalidOperationExceptionがスローされてメッセージがtestだったらテスト成功となります。

ちなみに以下のようにAssert.Throws()を使うこともできます。

var ex = Assert.Throws<InvalidOperationException>(TestMethod);
Assert.That(ex.Message, Is.EqualTo("test"));
例外がスローされないことをテストする

なお単純に例外がスローされないことをテストするには以下のように記述します。

using System;
using NUnit.Framework;

namespace ExampleTests
{
    public class ExampleTest
    {
        [Test]
        public void ExampleTest01()
        {
            void TestMethod()
            {
            }
            // 例外がスローされなかったらテスト成功
            Assert.That(TestMethod, Throws.Nothing);
        }
    }
}

以下のようにAssert.DoesNotThrow()を使うこともできます。

Assert.DoesNotThrow(TestMethod);

Unityのログ出力をテストする

それでは次にUnityのログに関連するテストの書き方をまとめます。

エラー出力されることをテストする

まずDebug.LogError()によりエラーログが出力されることを確認するにはLogAssert.Expect()を使用します。

using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;

namespace ExampleTests
{
    public class ExampleTest
    {
        [Test]
        public void ExampleTest01()
        {
            // 指定したメッセージのLogErrorが出力されることをテストする
            LogAssert.Expect(LogType.Error, "test error.");

            Debug.LogError("test error.");            
        }
    }
}

指定したメッセージのエラーログが出力されるとテストが成功になります。

また判定はフレームの最後に行われるため、コルーチンを使ったテストも考えると
テストコードよりもLogAssert.Expect()を先に書いておく必要があります。
例えば以下のテストは失敗します。

using System.Collections;
using UnityEngine;
using UnityEngine.TestTools;

namespace ExampleTests
{
    public class ExampleTest
    {
        [UnityTest]
        public IEnumerator ExampleTest01()
        {
            Debug.LogError("test error.");
            yield return null;
            
            // テスト対象のコードより前に書いておかないと判定されない
            // このテストは失敗します
            LogAssert.Expect(LogType.Error, "test error.");
        }
    }
}
他のログ出力をテストする

ちなみにLogAssert.Expect()の第一引数のLogTypeには以下の種類も用意されています。

  • LogType.Error
  • LogType.Assert
  • LogType.Warning
  • LogType.Log
  • LogType.Exception

したがって、例えば「特定の警告が出力されること」をテストしたりもできます。

正規表現でメッセージをテストする

またLogAssert.Expect()の第二引数のメッセージには正規表現を指定することもできます

using System.Text.RegularExpressions;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;

namespace ExampleTests
{
    public class ExampleTest
    {
        [Test]
        public void ExampleTest01()
        {            
            LogAssert.Expect(LogType.Error, new Regex("^test"));

            Debug.LogError("test error.");
        }
    }
}
すべてのログ出力がExpectされているかテストする

LogAssert.NoUnexpectedReceived()を使うと、すべてのログ出力がLogAssert.Expect()の対象となっているかをチェックできます。

using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;

namespace ExampleTests
{
    public class ExampleTest
    {
        [Test]
        public void ExampleTest01()
        {
            LogAssert.Expect(LogType.Error, "test error.");
            
            Debug.LogError("test error.");
            Debug.Log("test.");
            
            // この時点でLogAssert.Expectの対象になっていないログ出力が行われていたら失敗
            // この例ではDebug.Log("test")が対象になっていないのでテストは失敗する
            LogAssert.NoUnexpectedReceived();
        }
    }
}

このメソッドは呼び出した時点でチェックを行うようなので、
上の例のようにテストコードの最後に書く必要がある点に注意が必要です。

また、クラス内のすべてのメソッドにこれを適用したい場合には、
クラスにTestMustExpectAllLogsアトリビュートを付ければ同等の結果が得られます。

using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;

namespace ExampleTests
{
    // 全てのログ出力がLogAssert.Expectの対象になっていないとテストを失敗にする
    [TestMustExpectAllLogs]
    public class ExampleTest
    {
        [Test]
        public void ExampleTest01()
        {
            LogAssert.Expect(LogType.Error, "test error.");
            
            Debug.LogError("test error.");
            Debug.Log("test."); // これのせいでこのテストは失敗する
        }
    }
}
ログ出力によるテストの失敗をさせないようにする

そもそもエラー出力が行われてもテストを失敗扱いにしないようにするにはLogAssert.ignoreFailingMessagesをtrueにします。

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

namespace ExampleTests
{
    public class ExampleTest
    {
        [Test]
        public void ExampleTest01()
        {
            LogAssert.ignoreFailingMessages = true;
            
            // テストは失敗しない
            Debug.LogError("test error.");
            Debug.LogAssertion("test assertion.");
            Debug.LogException(new Exception());
        }
    }
}

なおこのフラグは各テストの終了後にデフォルト値(false)に戻るので各テストの先頭で設定する必要があります。

関連

light11.hatenadiary.com

参考

nunit.org

docs.unity3d.com