1.2K Views
September 26, 19
スライド概要
2019/9/25-6に開催されたUnite Tokyo 2019の講演スライドです。
長谷川 孝二(株式会社ディー・エヌ・エー)
こんな人におすすめ
・仕様変更やイージーミスによる手戻り、リリース遅延、詫び石などに悩まされている開発者およびテストエンジニア
受講者が得られる知見
・開発者テスト (Unit testing, Integration testing) の位置づけ・目的・ノウハウ
・ゲーム開発におけるテストコードのベストプラクティス
・Unity Test Runner及びテストツール・ライブラリの使いかた
Unityのイベント資料はこちらから:
https://www.slideshare.net/UnityTechnologiesJapan/clipboards
リアルタイム3Dコンテンツを制作・運用するための世界的にリードするプラットフォームである「Unity」の日本国内における販売、サポート、コミュニティ活動、研究開発、教育支援を行っています。ゲーム開発者からアーティスト、建築家、自動車デザイナー、映画製作者など、さまざまなクリエイターがUnityを使い想像力を発揮しています。
Unity Test Runner を活用して 内部品質を向上しよう 株式会社ディー・エヌ・エー 長谷川 孝二
自己紹介 - 長谷川 孝二 - DeNA SWETグループ 2019.2 〜 - 著書『iOSアプリテスト自動 化入門』など - Unityへの入り口はVR ※アイコンはイメージです 2
SWETグループとは (1/2) - Software Engineer in Test の略 - Google: SET, Microsoft: SDET - 他の横断的組織と連携し、プロダクト開発を サポート - 組織の名前であり、ロール 3
SWETグループとは (2/2) - テスト自動化の支援(アドバイザー) - テスト/検証ツール作成 - CI/CD、デバイスファーム - 形式手法、テスト技術などのR&D 4
本日お話すること 5
CEDEC 2019 『組織にテストを書く文化を根付か せる戦略と戦術』
『組織にテストをー』で語られていること - 開発者がテストコード(ユニットテスト)を書く 必要性 - テストが開発効率を上げるという根拠 - どこから取り掛かるべきかの指針 本セッションの前提となる知見が詰まっています まだ見ていない方はぜひCEDiLで! 7
でも、ゲーム開発は特殊だから… (ありがちな感想) 8
ゲーム開発は特殊なのか? - 特殊であることは否定しませんが、他の分野 もだいたいそれぞれ特殊 - ゲームの種類、アーキテクチャによっても事情 は異なる 9
ゲームでもテストを書くことが有効な具体例や ベストプラクティスを、本セッションで紹介します! 10
アジェンダ - 「テスト」に関する4つの誤解 - Unity Test Runner を使ってみよう - ゲーム開発向け ユニットテストパターン - ”テストを書く文化を根付かせる” 試み - Unity Test Runner Tips 11
「テスト」に関する 4つの誤解 12
誤解 1 「テスト」 == 「デバッグ」 13
「デバッグ」と「テスト」 - 主にゲーム開発で使われる「デバッグ」 - バグの発見 - 原因箇所の特定 - バグの修正 14
「デバッグ」と「テスト」 - ソフトウェア開発全般で言う「テスト」の目的 - バグの発見 - 対象ソフトウェアの品質レベルが十分であ ることを確認する - バグの作りこみを防ぐ “ISTQB The Certified Tester Foundation Level in Software Testing Syllabus”より 抜粋・意訳 15
「デバッグ」と「テスト」 - ソフトウェア開発全般で言う「テスト」の目的 - バグの発見 - 対象ソフトウェアの品質レベルが十分であ ることを確認する - バグの作りこみを防ぐ “ISTQB The Certified Tester Foundation Level in Software Testing Syllabus”より 抜粋・意訳 16
「デバッグ」と「テスト」 - ソフトウェア開発全般で言う「テスト」の目的 - バグの発見 - 対象ソフトウェアの品質レベルが十分であ ることを確認する - バグの作りこみを防ぐ “ISTQB The Certified Tester Foundation Level in Software Testing Syllabus”より 抜粋・意訳 17
対象ソフトウェアの品質レベルが十分であることを確認する - 正しく動くことを確認する - “品質を測る” 18
「デバッグ」と「テスト」 - ソフトウェア開発全般で言う「テスト」の目的 - バグの発見 - 対象ソフトウェアの品質レベルが十分であ ることを確認する - バグの作りこみを防ぐ “ISTQB The Certified Tester Foundation Level in Software Testing Syllabus”より 抜粋・意訳 19
バグの作り込みを防ぐ - リグレッション(デグレ、エンバグ) - 発見は早ければ早いほどよい 20
誤解 2 ビルドしたゲームを手で 操作するのがテスト 21
結合度の低いレベルで行なうテスト - 小さな単位で早期に確実に検証する - ユニットテスト、インテグレーションテスト - 手では操作できないので、自動テスト - Unity Test Runner - NUnit 22
ユニットテストの利点 - 素早く実行して、バグを早期に発見できる - 個々の部品の品質を上げておく - 再現の難しい条件を作り出しやすい - タイミング、乱数、GUIで指定できない値 23
誤解 3 テストを書けば品質が上がる 24
テストを書くだけでは品質は上がらない - テストは ”品質を測る” だけ - そもそも、品質の低いプロダクトにはテストが 書きにくい 25
テスタビリティ(テスト性) - テストのしやすさ(書きやすさ)についての 品質特性 - テストしやすいコードは、その時点でバグも少 なく可読性も高い 26
ルンバビリティ ※ルンバは、アイロボット コーポレイションの登録商標です 27
内部品質 - 外からのテストではわからない、コードの品質 - テスタビリティ(テスト性) - 保守性(可読性など) - 移植性 28
誤解 4 テストコードは、プロダクトを 開発した後から書く 29
テストを書くタイミング - プロダクトを開発しながらテストコードも書き、 実行する - テストしやすいプロダクトコードになる - バグが見つかるまでの時間が短くなる 30
テストコードは、建築における”足場” 31
Unity Test Runner を 使ってみよう 32
Unity Test Runner とは - Unity標準のユニットテスト実行環境 - Unity 2019.2 からはPackage化 - Unity Test Framework (UTF) - NUnit 3.0 ベースのテストコード 33
Unity Test Runner の実行環境 - Unityエディタで実行 - EditMode / PlayMode / Player(実機) - Test Runner ウィンドウ / CLI - JetBrains Rider で実行 - EditModeのみ 34
Unity Test Runner ウィンドウ (1/3) - Unityエディタの メニューから Window > General > Test Runner 35
Unity Test Runner ウィンドウ (2/3) - EditMode か PlayMode を選択して “Run All” 36
Unity Test Runner ウィンドウ (3/3) - EditMode か PlayMode を選択して “Run All” - 成否が表示される 37
EditMode と PlayMode - EditMode - Unityエディタ上で素早くテスト実行できる - PlayMode - Unityエディタのプレイモードで実行できる - 様々な注意事項(後述) 38
EditMode Tests (1/3) - テストコードの置き場所は2通り - Editorフォルダ - 任意のフォルダに Assembly Definition File を配置してEditorアセンブリにする 39
EditMode Tests (2/3) - Assembly Definition File 設定内容 - テスト対象のアセンブリ への参照 - Test Assemblies: on - Editor: on 40
EditMode Tests (3/3) - テストクラスに制約は無いが、テスト対象の粒 度に合わせるのが慣例 - メソッドに [Test] アトリビュートをつけたもの がテストメソッドと認識される 41
EditMode Tests の例 - テスト対象
using UnityEngine;
/// <summary>
/// 弾丸にまつわるドメインロジック
/// </summary>
public class Bullet
{
/// <summary>
/// 2つの<c>Vector3</c>が衝突したと判断できればtrueを返す
/// </summary>
public static bool IsHit(ref Vector3 a, ref Vector3 b)
{
// snip
}
}
// snip
42
EditMode Tests の例 - テストメソッド using NUnit.Framework; using UnityEngine; public class BulletTest { [Test] public void IsHit_notHit() { } } 43
EditMode Tests の例 - Verify using NUnit.Framework; using UnityEngine; public class BulletTest { [Test] public void IsHit_notHit() { Assert.That(actual, Is.False); } } 44
EditMode Tests の例 - Exercise using NUnit.Framework; using UnityEngine; public class BulletTest { [Test] public void IsHit_notHit() { var actual = Bullet.IsHit(ref a, ref b); Assert.That(actual, Is.False); } } 45
EditMode Tests の例 - Setup using NUnit.Framework; using UnityEngine; public class BulletTest { [Test] public void IsHit_notHit() { var a = new Vector3(100, 200, 300); var b = new Vector3(100, 200, 3000); var actual = Bullet.IsHit(ref a, ref b); Assert.That(actual, Is.False); } } 46
Assert のバリエーション Assert.That(actual, Assert.That(actual, Assert.That(actual, Assert.That(actual, Assert.That(actual, Is.True); Is.EqualTo(200)); Is.GreaterThan(100)); Is.LessThanOrEqualTo(300)); Is.InRange(100, 500)); Assert.AreEqual(200, actual); 47
[UnityTest] アトリビュート - 複数フレームにまたがるテストを記述できる - EditModeでは `EditorApplication.update` コールバックループで実行 - yield return には null しか指定できない - PlayModeではコルーチンで実行 48
PlayMode Tests - EditMode Testsとは別のア センブリ - テスト対象のアセンブリへの 参照 - Test Assemblies: on - Editor: off 49
PlayMode Tests の注意事項 (1/3) - テスト用の空のSceneファイルが生成・ロード される - テスト実行ごとのオーバーヘッド - UnityエディタがクラッシュするとScene ファイルが残ってしまう(まれによくある) 50
PlayMode Tests の注意事項 (2/3) - 一連のテスト実行の間、生成されたSceneは 使い回される - 適切にクリーンナップしていないと、後続の テストに影響 51
PlayMode Tests の注意事項 (3/3) - Sceneベースのテストを書くのはつらい - インテグレーションテストを書くのであれば、 PocoやAltUnityTesterを検討すべき 52
ゲーム開発向け ユニットテストパターン 53
テストの基本 54
テストの基本は「入力」と「出力」 入力 テスト 対象 55 出力
「入力」と「出力」の見極め - 「セーブしました」メッセージが出たからOKな のか? - クラッシュしなかったからOKなのか? - 適切なAssertが書けているか? - もととなるのは「テスト観点」 56
観点 - 観点によって、入力と出力の捉え方が変わる - 例えば「実行速度」が観点のときは? 57
価値の高いテストを書く 58
価値の高いテスト - リスクが高いところ - クラッシュ、ショーストッパー - 課金まわり - 見落としがちなところ - あまり通らないルート、画面 59
重要でないテスト - 副作用的なもの、目で見てわかるもの - エフェクト、SE 60
組み合わせ条件を減らす 61
組み合わせ条件が多すぎるケース - 要素が多ければテストも複雑になる - 当たり判定、ダメージ、破壊判定を行なう - 入力:敵の座標とコライダ、HP、弾の座標と コライダ、威力、… 62
責務を分ける - プロダクトコード側の責務を適切に分割する ことで、個々のコンポーネントが扱う要素は減 る - 当たり判定を行うメソッド - ダメージ・破壊判定を行うメソッド 63
責務を分けたらコールスタックが増えて 性能が出ないのでは? 64
遅くなるのでは? - 本当にボトルネックになるところは少ない - 本当にボトルネックになる場合 - コンパイラの最適化を信じる - ref - インライン化 65
パフォーマンステスト 66
パフォーマンステスト - ユニットテストの段階から意識する - メソッド単位に実行時間を測定しておく - PlayModeでfpsを測定する 67
パフォーマンステスト - とはいえ、実行環境に依存するのでピーキー にチューニングするものでもない - 魔除けのお守りくらいの感じ - 時間でなく、AllocatingGCMemoryで測る 68
他のオブジェクトへの依存 69
依存オブジェクト - テスト対象が内部で使用している他のオブ ジェクト - テスト結果に影響するもの(間接入力) - 依存オブジェクトに渡した引数を評価したい (間接出力) 70
テストダブル - スタントダブル - 影武者 - 受け身なイメージ? - 勝手なことをしそうなイメージ? - 詳しい定義は『xUnit Test Patterns』を参照 71
テストダブル (1/4) 72
テストダブル (2/4) 73
テストダブル (3/4) 74
テストダブル (4/4) 75
仕様変更のたびにテストが壊れる 76
よくある誤解 - ✗ 仕様変更があるからテストは書けない - ○ 仕様変更があるからテストで保護しておく - YAGNI - 保守しやすいテストコード - 邪魔になったら削除すればいい 77
ありがちなこと - 仕様ではなく、実装のテストを書いてしまう - 実装は複数パターン考えられる - 変更で壊れやすいテストになる 78
副作用は検証しないという選択肢 - 本当に重要な、壊れては困る部分のみ検証す る - 当たり判定、破壊判定 - 副作用は目視に頼る - エフェクト、サウンド 79
あえて定石から外れる - 境界値を攻めすぎない - 条件網羅を追いすぎない 80
テストコードは捨ててもいい - 無理にメンテナンスするより、捨ててしまう - TDDで過剰に書いたものを取り除く 81
テストコードも構造化 - オブジェクトの依存関係が深くなりがち - 似たような初期化処理が増える - 面倒を避けようとテストダブルを多用すると、 変更に弱いテストコードになる 82
“テストコードはガラスのような壊れやすいもの ではなく、竹のようにしなやかで柔軟性の高い ものを目指すべき” Jon Reid 83
テストコードは、建築における”足場” 84
竹で足場を組む なぜ香港の工事現場は、竹で足場を組むのか? https://www.itmedia.co.jp/makoto/articles/0811/14/news049.html 85
”組織にテストを書く文化を 根付かせる” 試み 86
SWETグループとは(再掲) - Software Engineer in Test の略 - Google: SET, Microsoft: SDET - 他の横断的組織と連携し、プロダクト開発を サポート - 組織の名前であり、ロールでもある 87
SWETの採用したアプローチ - 共通的フレームワークのリファレンス実装に 対し、テストコードのサンプルを書く - バグを摘出(自動テストで新種のバグが見つ かるのは稀です) - ほかへの引き合いにつながった 88
タイミングがよかった 89
「ボトムアップではじめる」のは難しい - どの箇所でも効率よくテストが書けるわけで はない - 無理に書いたテストはよくないテストだったり、 ROIが低かったり 90
Semper Paratus “常に備えよ” - 米沿岸警備隊の格言 91
Unity Test Runner Tips 92
IEqualityComparer: 誤差を許容して比較 [Test] public void TestVector2EqualityComparer() { var actual = new Vector3(10f, 0f); var expected = new Vector3(10.7f, 0f); var comparer = new Vector2EqualityComparer(0.1f); Assert.That(actual, Is.EqualTo(expected).Using(comparer)); } ほかに、Color, Float, Quaternion, Vector3, Vector4 があります 93
例外の発生を確認するテスト Assert.That( () => ExceptionSample.ThrowIndexOutOfRangeException(), Throws.TypeOf<NullReferenceException>()); NullReferenceExceptionが発生したらsuccess 94
パラメタライズドテスト [Test] public void ParameterizedSampleTest( [Values(100, 200, 300)] int a) { var actual = sut(a); // snip } Valuesで指定したパターンが実行される 95
パラメタライズド・組み合わせテスト [Test, Combinatorial] public void CombinatorialSampleTest( [Values(100, 200, 300)] int a, [Values(10, 20, 30)] int b) { var actual = sut(a, b); // snip } 複数の[Values]指定の総当たり 96
[Ignore] アトリビュート - なんらかの理由(テスト対象が未実装など)で 実行対象から除外したいとき - [Ignore(“comment“)] でコメントも書ける 97
internal メソッドのテスト - Editor下は別アセンブリになるので、テスト対象の internalメソッドを呼べない - テスト対象アセンブリ内で [assembly: InternalsVisibleTo(“Assembly-CSharpEditor”)] を宣言することで呼べるようになる 98
private メソッドのテスト - 原則、テストコードからアクセスできない - リフレクションという手段はあるが避けるべき (とても壊れやすいテストになる) - 適切にクラスが分割・委譲されていれば困ら ないはず 99
ブログでも情報発信していきます - DeNA Testing Blog (SWET) https://swet.dena.com/ - 個人ブログ https://www.nowsprinting.com/ 100