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

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

いも
December 12, 2021

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

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

いも

December 12, 2021
Tweet

More Decks by いも

Other Decks in Programming

Transcript

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  5. 面倒なところを先回りする
    後々テスト書くときに面倒になりそうだな~という部分を先に対応す

    シングルトンの撤廃
    テスト領域を明確にする
    通信は手厚くする
    データを用意しやすくする
    5

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    https://speakerdeck.com/adarapata/humble-object-patternnahua 9

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  15. データ生成はテスト用のFactoryメソッドを用意してあげる
    public class TestUtility {
    public static UserCharacter CreateCharacter(int id = 1, int masterId = 100) {}
    }
    ScriptableObjectなどの外部データ化もあり
    15

    View full-size slide

  16. リファクタし続ける
    テストはコード品質を上げない
    リファクタにこそ価値がある
    開発中の「本当はこうした方がよい」は山ほどある
    大体後からやるとつらい
    時には影響範囲の大きいドラスティックな改修もやる必要がある。

    その際にテストコードを指標にしてやるべきことを定める
    16

    View full-size slide

  17. 体験例:通信の基盤の改善
    通信クラスのレスポンスの構造を変えたいが、既にかなり利用されて
    いるので多くの箇所に影響してしまう。
    以下の手順で改修した
    1. おもむろに実装を変える
    2. テストが大量に落ちる(ここで影響範囲がある程度見える)
    3. 全て [Ignore] を書いてスキップさせる
    4. 1個ずつ直してPR出していく
    ゴールが見えるようになることで、見積もりの精度と勇気をもらう
    17

    View full-size slide

  18. テストもリファクタし続ける
    テストコードもコードでありリファクタの対象
    毎回書いてるコードは纏めていく
    テストを書くことを億劫にさせない
    不要となったテストケースは削除していく
    仕様が変わったけどなんとなく生きてるやつとか
    頻繁に落ちて直してるケースなど
    18

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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


    21

    View full-size slide

  22. TDDリポジトリを使ったハンズオン
    https://github.com/adarapata/UnityTDD_Example
    ハンズオン用に作ったRepo
    サンプルは業務で実体験したものを抽象化したもの
    Unityゲーム開発者ギルド(UGDG)や副業などで使いました
    ご自由にお使いください
    22

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    27

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  30. 大きなコードとの向き合い方
    例:「HP10%以下で強力な攻撃を放つ敵」という巨大なクラスの場合
    public class BigEnemy {
    public void Update() {
    /*
    すごく長い処理がいっぱい
    */
    if(life / maxLife < 0.1F){
    StrongAttack(); // 攻撃ダメージ計算とか演出とか
    }
    }
    }
    30

    View full-size slide

  31. 全てを解決しようとすると難しい
    HP10%以下になったらちゃんと動く?
    攻撃処理は想定通り動く?
    そもそもEnemyは単体で動くの?
    様々な問題と向き合うことになり、頭が痛くなる・・。
    31

    View full-size slide

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

    32

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  35. 引用
    P27. Agile Testing Quadrants

    https://www.informit.com/articles/article.aspx?
    p=2253544&ranMID=24808
    35

    View full-size slide