Slide 1

Slide 1 text

Zenject でのテスト Gotanda.unity #5 2018/03/20

Slide 2

Slide 2 text

自己紹介 いも(@adarapata) ミクシィ XFRAG ゲー ムプログラマ 入社三日目 最近GodotEngine が楽しい

Slide 3

Slide 3 text

今日の内容 Unity 標準のテストの書き方と、Zenject を使って いる場合のテストの書き方の違いを知る。 ※ Unity 内でのテストコー ドが必要かどうかについて は今回は話しません。

Slide 4

Slide 4 text

Zenject is https://github.com/modesttree/Zenject Unity で使えるDI フレー ムワー ク 依存関係をすっきりさせてくれる かせさんの記事がわかりやすい http://yutakaseda3216.hatenablog.com/entry/201 7/04/17/124612

Slide 5

Slide 5 text

Unity のTest 書いてますか?

Slide 6

Slide 6 text

Unity でのテスト方法 EditModeTest エディタ上で動くテスト PlayModeTest シー ン上で動作確認できるテスト

Slide 7

Slide 7 text

Zenject in Unity でのテスト方 法 EditModeTest == Zenject Unit Test エディタ上で動くテスト PlayModeTest == Zenject Integration Test シー ン上で動作確認できるテスト

Slide 8

Slide 8 text

Foo がIBar に依存してる処理( 通常) public class Foo { public IBar bar; public int CallBar() // このメソッドをテストしたい { return bar.Bar() * 2; } } public interface IBar { int Bar(); }

Slide 9

Slide 9 text

EditModeTest public class FooEditModeTest { class MockBar : IBar { public int Bar() { return 5; } } [Test] public void CallBarTest() { Foo foo = new Foo { bar = new MockBar() }; Assert.AreEqual(10, foo.CallBar()); } } Foo に流し込むモック用クラスを作る必要がある

Slide 10

Slide 10 text

Foo がIBar に依存してる処理 (Zenject) public class Foo { [Inject] public IBar bar { get; private set; } public int CallBar() // このメソッドをテストしたい { return bar.Bar() * 2; } } public interface IBar { int Bar(); }

Slide 11

Slide 11 text

[TestFixture] public class FooUnitTest : ZenjectUnitTestFixture { [Test] public void CallBarTest() { var mock = new Mock(); mock.Setup(b => b.Bar()).Returns(5); Container.BindInstance(mock.Object); Container.Bind().FromNew().AsSingle(); var foo = Container.Resolve(); Assert.AreEqual(10, foo.CallBar()); } } Container 経由でインスタンスを生成できる Container のモック機能が使える

Slide 12

Slide 12 text

Mock 便利 Moq ライブラリが提供する機能 https://github.com/moq モックを簡単に流し込めるのでテストが楽 OptionalExtras に入ってる // 何も返さなくていいとき Container.Bind().FromMock() // 明示的にメソッドの返り値をモックしたい時 var mock = new Mock(); mock.Setup(b => b.Bar()).Returns(5); Container.BindInstance(mock.Object);

Slide 13

Slide 13 text

MonoBehavior が必要な処理 public class SpaceShip : MonoBehaviour { public Vector3 Velocity { get; set; } public void Update() { transform.position += Velocity * Time.deltaTime; } } Velocity が設定されたら動くかテストしたい

Slide 14

Slide 14 text

public class SpaceShipPlaymodeTest { [UnityTest] public IEnumerator SpaceShipPlaymodeTestWithEnumeratorPasses var spaceShip = new GameObject("ship") .AddComponent(); spaceShip.Velocity = Vector3.up; Assert.AreEqual(spaceShip.transform.position, Vector3.ze yield return new WaitForSeconds(1F); Assert.Greater(spaceShip.transform.position.y, 0F); } }

Slide 15

Slide 15 text

MonoBehavior が必要な処理(Zenject) public class SpaceShip : MonoBehaviour { [Inject] public Vector3 Velocity { get; private set; } public void Update() { transform.position += Velocity * Time.deltaTime; } }

Slide 16

Slide 16 text

Zenject in PlayMode public class SpaceShipTests : ZenjectIntegrationTestFixture { [UnityTest] public IEnumerator TestVelocity() { PreInstall(); Container.Bind() .FromNewComponentOnNewGameObject() .AsSingle().WithArguments(Vector3.up); PostInstall(); var spaceShip = Container.Resolve(); Assert.AreEqual(spaceShip.transform.position, Vector3.ze yield return null; Assert.Greater(spaceShip.transform.position.y, 0F); } }

Slide 17

Slide 17 text

ZenjectIntegrationTestFixture がDI するためにいい感 じのメソッドを持っている PreInstall テスト用の準備をしてくれる Validation とかContext の生成とか PostIntall バインドしたオブジェクトを注入する Run all in player には対応しておらず実機では走 らないので注意

Slide 18

Slide 18 text

所感 普通のテストとそんなにやり方は変わらない Zenject で書くべき! というかはInject してたら必 然的にテストもContainer 使わざるを得なくなるよ ねというお気持ち でもMoq をサクッと使えるのは便利かも

Slide 19

Slide 19 text

余談1 unity-uitest https://github.com/taphos/unity-uitest uGUI 周りのテストを書きやすくしてくれるライブ ラリ クリックイベント辺りをやりやすくしてくれる UITest を継承する必要があるので ZenjectIntegrationTest だと使いづらい

Slide 20

Slide 20 text

public class TestExample : UITest { [UnityTest] public IEnumerator Example() { // シー ン読み込み yield return LoadScene("TestableScene"); // Button オブジェクトを押したことにする yield return Press("Button"); // Foo/Text のラベルの文字を確認する yield return AssertLabel("Foo/Text", "Pressed!"); yield return Press("Close"); // FooWindow コンポー ネントがついたオブジェクトが非アクティブになる yield return WaitFor(new ObjectDisappeared( } }

Slide 21

Slide 21 text

余談2 RuntimeUnitTestToolkit https://github.com/neuecc/RuntimeUnitTestToolkit @neuecc さんの作ったテストライブラリ 普通のシー ン上でテストを走らせてくれる アサー ションを再実装してシー ンで動くようにし ている シー ン上での動作なので実機での確認も可能

Slide 22

Slide 22 text

No content