Zenjectとテスト

1d1580fb0945b0ffadff18e28bead3c5?s=47 いも
March 20, 2018

 Zenjectとテスト

Gotanda.unity #5で発表するやつです

1d1580fb0945b0ffadff18e28bead3c5?s=128

いも

March 20, 2018
Tweet

Transcript

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

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

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

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

    http://yutakaseda3216.hatenablog.com/entry/201 7/04/17/124612
  5. Unity のTest 書いてますか?

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

  7. Zenject in Unity でのテスト方 法 EditModeTest == Zenject Unit Test

    エディタ上で動くテスト PlayModeTest == Zenject Integration Test シー ン上で動作確認できるテスト
  8. Foo がIBar に依存してる処理( 通常) public class Foo { public IBar

    bar; public int CallBar() // このメソッドをテストしたい { return bar.Bar() * 2; } } public interface IBar { int Bar(); }
  9. 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 に流し込むモック用クラスを作る必要がある
  10. 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(); }
  11. [TestFixture] public class FooUnitTest : ZenjectUnitTestFixture { [Test] public void

    CallBarTest() { var mock = new Mock<IBar>(); mock.Setup(b => b.Bar()).Returns(5); Container.BindInstance(mock.Object); Container.Bind<Foo>().FromNew().AsSingle(); var foo = Container.Resolve<Foo>(); Assert.AreEqual(10, foo.CallBar()); } } Container 経由でインスタンスを生成できる Container のモック機能が使える
  12. Mock<T> 便利 Moq ライブラリが提供する機能 https://github.com/moq モックを簡単に流し込めるのでテストが楽 OptionalExtras に入ってる // 何も返さなくていいとき

    Container.Bind<IBar>().FromMock() // 明示的にメソッドの返り値をモックしたい時 var mock = new Mock<IBar>(); mock.Setup(b => b.Bar()).Returns(5); Container.BindInstance(mock.Object);
  13. MonoBehavior が必要な処理 public class SpaceShip : MonoBehaviour { public Vector3

    Velocity { get; set; } public void Update() { transform.position += Velocity * Time.deltaTime; } } Velocity が設定されたら動くかテストしたい
  14. public class SpaceShipPlaymodeTest { [UnityTest] public IEnumerator SpaceShipPlaymodeTestWithEnumeratorPasses var spaceShip

    = new GameObject("ship") .AddComponent<SpaceShip>(); spaceShip.Velocity = Vector3.up; Assert.AreEqual(spaceShip.transform.position, Vector3.ze yield return new WaitForSeconds(1F); Assert.Greater(spaceShip.transform.position.y, 0F); } }
  15. MonoBehavior が必要な処理(Zenject) public class SpaceShip : MonoBehaviour { [Inject] public

    Vector3 Velocity { get; private set; } public void Update() { transform.position += Velocity * Time.deltaTime; } }
  16. Zenject in PlayMode public class SpaceShipTests : ZenjectIntegrationTestFixture { [UnityTest]

    public IEnumerator TestVelocity() { PreInstall(); Container.Bind<SpaceShip>() .FromNewComponentOnNewGameObject() .AsSingle().WithArguments(Vector3.up); PostInstall(); var spaceShip = Container.Resolve<SpaceShip>(); Assert.AreEqual(spaceShip.transform.position, Vector3.ze yield return null; Assert.Greater(spaceShip.transform.position.y, 0F); } }
  17. ZenjectIntegrationTestFixture がDI するためにいい感 じのメソッドを持っている PreInstall テスト用の準備をしてくれる Validation とかContext の生成とか PostIntall

    バインドしたオブジェクトを注入する Run all in player には対応しておらず実機では走 らないので注意
  18. 所感 普通のテストとそんなにやり方は変わらない Zenject で書くべき! というかはInject してたら必 然的にテストもContainer 使わざるを得なくなるよ ねというお気持ち でもMoq

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

    だと使いづらい
  20. 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<FooWindow>( } }
  21. 余談2 RuntimeUnitTestToolkit https://github.com/neuecc/RuntimeUnitTestToolkit @neuecc さんの作ったテストライブラリ 普通のシー ン上でテストを走らせてくれる アサー ションを再実装してシー ンで動くようにし

    ている シー ン上での動作なので実機での確認も可能
  22. None