目次
はじめに
- この一連の記事は
- UnityのUnity Test Frameworkを使ったテストに関して調べたメモ書きに補足を足したもの
- Unityのテスト、およびDIコンテナ、モックライブラリの基本的な使い方やそれぞれの役割など入門レベルの解説
- ライブラリのリファレンス的な使い方などについては公式や他の記事参照
- 実機テストやCIは含まない
- TDDに関しては考慮しない
シリーズの目次
- Unity Unit Test(単体テスト)入門
- Unity Unit Test(単体テスト)入門 - Unity Test Framework
- Unity Unit Test(単体テスト)入門 - Extenject(Zenject)
- Unity Unit Test(単体テスト)入門 - VContainer
- Unity Unit Test(単体テスト)入門 - Moq(この記事)
- Unity Unit Test(単体テスト)入門 - NSubstitute
- Unity Unit Test(単体テスト)入門 - 番外編 UniRx
環境
- Unity 2021.3.29f1
- Package
- Test Framework 1.3.8
- com.unity.test-framework / 1.3.8
- Extenject 9.2.0
- VContainer 1.13.2
https://github.com/hadashiA/VContainer.git?path=VContainer/Assets/VContainer#1.13.2
- Moq
- 4.7.99 (Extenject内蔵)
- 4.20.69 (NuGet)
- NSubstitute
- v2.0.3.0 for .Net v4.5 (Extenject内蔵)
- 5.0.0 (NuGet)
https://github.com/neuecc/UniRx.git?path=Assets/Plugins/UniRx/Scripts
https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask
- Test Framework 1.3.8
Moq
手順
- Moqを追加する
- Extenject(Zenject)からの場合
- AutoMocking.zipをインポートする
- AutoMocking.zipを解凍、さらにMoq-Net46.zipも解凍する
- AutoMockingフォルダごとTestFrameworkフォルダに配置する
- Zenject-TestFramework.asmdefに
Moq.dll
の参照を追加する
- AutoMocking.zipをインポートする
- nugetからの場合
- パッケージをダウンロード
- 拡張子をzipに書き換えて解凍
- それぞれ.NET Standard 2.1 or 2.0 のdllをプロジェクトに追加する
- それぞれのasmdefで、
AutoReference
のチェックを外し、Define Constraints
にUNITY_INCLUDE_TESTS
を追加する - UnityでMoqを使う (Unity2021バージョン) - すぎしーのXRと3DCG
- Extenject(Zenject)からの場合
基本的な使い方
- テストのasmdefに、
Moq.dll
の参照を追加する テストコードで
Mock
を使ってテストするインターフェース
public interface IShield { string Name { get; } uint DefencePower { get; } void AddEffect(int value); uint CalcDefencePower(); }
- テストコード
[TestFixture] public class MoqUnitTest { [Test] public void MockObjectからのOutputの確認() { var mock = new Mock<IShield>(); // プロパティのGet mock.Setup(x => x.Name).Returns("Name"); // メソッドの戻り値 mock.Setup(x => x.CalcDefencePower()).Returns(100); IShield shield = mock.Object; Assert.That(shield.Name, Is.EqualTo("Name")); Assert.That(shield.CalcDefencePower(), Is.EqualTo(100)); } [Test] public void MockObjectへのInputの確認() { var mock = new Mock<IShield>(); mock.Object.AddEffect(-10); mock.Verify(x => x.AddEffect(-10), Times.Once); } [Test] public void Mockのコールバック() { LogAssert.Expect(LogType.Log, "AddEffect called."); var mock = new Mock<IShield>(); mock.Setup(x => x.AddEffect(It.IsAny<int>())) .Callback<int>(x => Debug.Log($"AddEffect called.")); mock.Object.AddEffect(0); mock.Verify(x => x.AddEffect(It.IsAny<int>()), Times.AtLeastOnce); } }
サンプル
アプリ側
public interface ISystemClock { DateTime Now { get; } } public class Ticket { private ISystemClock _clock; private uint _expireDays; public Ticket(ISystemClock clock, uint expireDays) { _clock = clock; _expireDays = expireDays; } public DateTime Issue() { return _clock.Now.AddDays(_expireDays); } }
with Extenject(Zenject)
[TestFixture] public class MoqZenjectUnitTest : ZenjectUnitTestFixture { [Test] public void SimpleTest() { var mock = new Mock<ISystemClock>(); mock.Setup(x => x.Now).Returns(new DateTime(2023, 1, 1)); Container.BindInstance(mock.Object) .AsTransient(); Container.Bind<Ticket>() .AsTransient() .WithArguments(10u); var target = Container.Resolve<Ticket>(); Assert.That(target.Issue, Is.EqualTo(new DateTime(2023, 1, 1).AddDays(10))); } }
with VContainer
[TestFixture] public class MoqVContainerTest { [Test] public void SimpleTest() { var mock = new Mock<ISystemClock>(); mock.Setup(x => x.Now).Returns(new DateTime(2023, 1, 1)); var builder = new ContainerBuilder(); builder.RegisterInstance(mock.Object); builder.Register<Ticket>(Lifetime.Transient) .WithParameter(typeof(uint), 10u); var container = builder.Build(); var target = container.Resolve<Ticket>(); Assert.That(target.Issue, Is.EqualTo(new DateTime(2023, 1, 1).AddDays(10))); } }