Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Unityテスト活動のふりかえり

1d1580fb0945b0ffadff18e28bead3c5?s=47 いも
December 12, 2021

 Unityテスト活動のふりかえり

【年末だよ】Unity お・と・なのLT大会 2021
https://meetup.unity3d.jp/jp/events/1337

1d1580fb0945b0ffadff18e28bead3c5?s=128

いも

December 12, 2021
Tweet

More Decks by いも

Other Decks in Programming

Transcript

  1. ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Unityテスト活動のふりかえり いも @adarapata 2021/12/12【年末だよ】Unity お・と・なのLT大会 2021 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1

  2. 自己紹介 いもです @adarapata 仕事でゲームとか作ってる DIとかテストが好きです 御朱印巡りはじめました https://adarapata.com 2

  3. Unityテスト活動のふりかえり 公私で行ってきたUnityでのテスト関連について雑にふりかえる 実践編 自身がテストを書いていく上で意識しながら行ったことの話 啓蒙編 他者にテストを書いてもらう上で意識しながら行ったことの話 3

  4. 実践編 今回は以下の例での話をします スマートフォンゲーム開発 複数人で開発している 数年運用する想定である 一般的にアウトゲームと呼ばれる部分を中心とする E2Eの話はしません 4

  5. 面倒なところを先回りする 後々テスト書くときに面倒になりそうだな~という部分を先に対応す る シングルトンの撤廃 テスト領域を明確にする 通信は手厚くする データを用意しやすくする 5

  6. シングルトンの撤廃 みんな大好きシングルトン staticなインスタンスは交換しにくい MonoBehaviourのシングルトンはシングルトンじゃない説 大体テスト書こうとしたときにシングルトンは障害となりやすいの で、最初から利用しない方針にする。 6

  7. 対応例 そもそもシングルトンインスタンスを作らなくていいような仕組み が必要 DIできたら基本的に必要なくなりそう VContainerを導入 https://vcontainer.hadashikick.jp 今回欲しいのはDIコンテナのみなのでVContainerは丁度よい 7

  8. テスト領域を明確にする 全部書くのは大変で旨味が少ない MonoBehaviourが絡むとテストしにくい できないことはない プロダクトがカバーしたい範囲はどこなのかを洗い出す 8

  9. 対応例 今回は画面に対してModel-View-Presenter(MVP)構造 Presenter-Viewは書かない ViewはMonoBehaviourとして持っているので書くのが大変 PresenterはViewを直接持つので実質MonoBehaviourへの依存 上記は伝搬と出力くらいのコードにしましょう それ以外のクラスは書けるようにしていきましょう 方針を定めることで、テストできる領域にコードを寄せていく。 refs 「HumbleObjectPatternな話」

    https://speakerdeck.com/adarapata/humble-object-patternnahua 9
  10. 通信を手厚くする アウトゲームの中枢に入ってくるやつ 外部との通信が入るとテスト難易度は高い テストを書かなかったとしても、開発中の通信偽装は欲しい サーバーができていないくても進めれる仕組みは欲しい この辺は実装前からオブジェクトを差し替えられる前提で作っておい たほうが楽 10

  11. 対応 通信クラスを抽象化してモックできるようにする モック用通信クラスを作る 一例 https://speakerdeck.com/adarapata/unityyong- mockclientfalsehua レスポンスを外部から流し込める機能があればとりあえずOK UnitTest用に使ってたが、アプリケーション側でそのまま使えそうだ ったので転用できた。 11

  12. データを用意しやすくする テスト対象が依存しているデータをサクッと準備したい 読み込み方、更新方法が特殊だととてもテストしにくい 必ずサーバーから取るとか 巨大なDataManagerを持つと更に辛い Characterデータだけ欲しいのにShopデータを用意するのは嫌 ゲームで都度外部IOは体験が悪いので基本はオンメモリにしたい 上記の理由から、シンプルに1つの種類のデータを保持するクラスだ けあれば嬉しそう。 12

  13. 対応例 データの取得・更新はRepositoryを用意するという方針を取った public interface IUserCharacterRepository { void Update(List<UserCharacter> characters); UserCharacter

    Get(UserCharacterId id); } public interface IMasterCharacterRepository { MasterCharacter Get(MasterCharacterId id); } 13
  14. 外からの流し込みをしやすくするために保存・取得以外行わない 1つのRepositoryは極力1データクラスのみ取り扱う Repositoryは他のRepositoryを見ない ここを許容すると循環参照生まれがち 複数のRepositoryを取り扱いたいなら素直に上位のクラスを作る public class CharacterFinder { private

    readonly IUserCharacterRepository _userRepository; private readonly IMasterCharacterRepository _masterRepository; public (MasterCharacter, UserCharacter) FindCharacter(UserCharacterId id) {} } 14
  15. データ生成はテスト用のFactoryメソッドを用意してあげる public class TestUtility { public static UserCharacter CreateCharacter(int id

    = 1, int masterId = 100) {} } ScriptableObjectなどの外部データ化もあり 15
  16. リファクタし続ける テストはコード品質を上げない リファクタにこそ価値がある 開発中の「本当はこうした方がよい」は山ほどある 大体後からやるとつらい 時には影響範囲の大きいドラスティックな改修もやる必要がある。 その際にテストコードを指標にしてやるべきことを定める 16

  17. 体験例:通信の基盤の改善 通信クラスのレスポンスの構造を変えたいが、既にかなり利用されて いるので多くの箇所に影響してしまう。 以下の手順で改修した 1. おもむろに実装を変える 2. テストが大量に落ちる(ここで影響範囲がある程度見える) 3. 全て

    [Ignore] を書いてスキップさせる 4. 1個ずつ直してPR出していく ゴールが見えるようになることで、見積もりの精度と勇気をもらう 17
  18. テストもリファクタし続ける テストコードもコードでありリファクタの対象 毎回書いてるコードは纏めていく テストを書くことを億劫にさせない 不要となったテストケースは削除していく 仕様が変わったけどなんとなく生きてるやつとか 頻繁に落ちて直してるケースなど 18

  19. 良かった所 基本的にはテストを書けるという環境を用意できた 大きな変更に対して強い意志を持ててる 改善できそうな所 未だにMonoBehaviourのテストの答えが出ない ユニットテストを書きたかったらUnityが提供するコンポーネント から距離を置くという判断になったが、それが適切であるか Prefabに対してのテストという観点を設けていいかもしれない Addressable使ってるなら読み込みは楽だしいけるかも 19

  20. 啓蒙編 プライベートで他の人がUnityでテストを書けるようになるための活 動を行っていました テストコードとTDDの座学 TDDリポジトリを使ったハンズオン ペアプロ・モブプロでのTDD 20

  21. なぜやってるのか(抜粋) https://adarapata.hatenablog.com/entry/2021/09/15/233534 ゲーム開発はソフトウェア開発の中でもかなり複雑性が高いと感 じています。そして性質上変更頻度も高い。ゲームごとに作り方 が変わる以上これは仕方ないことだと考えています。だからこそ より変化に対応できるようにテスタビリティの高いコードになっ ていてほしいと考えていますがその難易度、そしてそもそものテ ストコード文化というものがないためにテストコードが書かれて いることは少ないです。 まずはUnity界隈でテストコードを書

    く、という文化を作っていきたい。そんな気持ちで活動を行って います。 “ “ 21
  22. TDDリポジトリを使ったハンズオン https://github.com/adarapata/UnityTDD_Example ハンズオン用に作ったRepo サンプルは業務で実体験したものを抽象化したもの Unityゲーム開発者ギルド(UGDG)や副業などで使いました ご自由にお使いください 22

  23. ペアプロ・モブプロでのTDD 前述のハンズオンをモブプロしたり 特定の業務コードに対してペア・モブしたり 狙いとしては、テストに対して個人ではなくチームで関心を持って もらうこと どうテストを書いたらいいかを話し合える土壌づくり https://speakerdeck.com/adarapata/wakatutaqi- ninarumobupuroguramingu 23

  24. 活動の所感 テストを書く際に次のような課題が発生するケースが多かった 即物的な誘惑 レガシーに対しての踏み出し方 24

  25. 即物的な誘惑 1+1=2のテストを書いても仕方がない 即役に立つようなテストを書きたい 外部品質に直結する粒度で書きたくなる こうなるとテストの粒度が大きくなり、難易度も上がってしまう。 25

  26. 対策 バグを減らすという直接的な目的に囚われがち ユニットテスト ≠ バグを減らすではないことの徹底 アジャイルテスト4象限のお話 26

  27. レガシーに対しての踏み出し方 すでにリリースされたタイトルでテストコードを書き始めるのは大変 往々にしてテスタビリティが低い 開発を止めることはできない ガッツリテストコードを書く時間を用意しにくい リファクタリングも同様 日々の開発に少しずつテストを書くという習慣をつけていく必要があ る 27

  28. 学習のためのテスト戦略 なにはともあれ書けそうなものを探してみる。 staticメソッドのテスト staticメソッドは状態を持たない AのときにBであるというシンプルなテストを書きやすい ここから初めてテストを書くという感覚を身に付けていく ただしシングルトンは除く 28

  29. 学習のためのテスト戦略 書けない!と思ったときに問題を分析していく 何をテストしていいのかわからないから 目の前のクラスの役割は理解できているか? テストするための依存オブジェクトが準備できないから クラスが責務を負いすぎていないか? テストの記法がわからないから みんなでNUnitのページ見ましょう 29

  30. 大きなコードとの向き合い方 例:「HP10%以下で強力な攻撃を放つ敵」という巨大なクラスの場合 public class BigEnemy { public void Update() {

    /* すごく長い処理がいっぱい */ if(life / maxLife < 0.1F){ StrongAttack(); // 攻撃ダメージ計算とか演出とか } } } 30
  31. 全てを解決しようとすると難しい HP10%以下になったらちゃんと動く? 攻撃処理は想定通り動く? そもそもEnemyは単体で動くの? 様々な問題と向き合うことになり、頭が痛くなる・・。 31

  32. 問題の分割統治 「HPがn%以下になったことを検知できる機能」にフォーカスを当 てることはできる そこだけクラスを切り出し、テストを書く 動作を保証できたら該当箇所を差し替える 大きなコードを小さな機能に分解していき各個撃破で問題を潰してい く 32

  33. 良かった所 少なからずTDDへの興味・理解を示す人がいた ライブラリを理解するためにテスト書いたり まったくわからない状態から「書いてみる」に進められた 改善できそうな所 テストコードの書き方とTDDを同時に教えていた テストコード自体も初めての人が多かったので 場合によっては初モブプロも同時に教えることになった 学習難易度が上がり、全員が理解できたとは言えない状況だった。 33

  34. 最後に テストはゲームを面白くしないが、もっと面白くできると思ったとき に実現するための支えとなる。 34

  35. 引用 P27. Agile Testing Quadrants https://www.informit.com/articles/article.aspx? p=2253544&ranMID=24808 35