【Unite Tokyo 2019】Unity Test Runnerを活用して内部品質を向上しよう

858 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

profile-image

リアルタイム3Dコンテンツを制作・運用するための世界的にリードするプラットフォームである「Unity」の日本国内における販売、サポート、コミュニティ活動、研究開発、教育支援を行っています。ゲーム開発者からアーティスト、建築家、自動車デザイナー、映画製作者など、さまざまなクリエイターがUnityを使い想像力を発揮しています。

シェア

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

Unity Test Runner を活用して 内部品質を向上しよう 株式会社ディー・エヌ・エー 長谷川 孝二

2.

自己紹介 - 長谷川 孝二 - DeNA SWETグループ 2019.2 〜 - 著書『iOSアプリテスト自動 化入門』など - Unityへの入り口はVR ※アイコンはイメージです 2

3.

SWETグループとは (1/2) - Software Engineer in Test の略 - Google: SET, Microsoft: SDET - 他の横断的組織と連携し、プロダクト開発を サポート - 組織の名前であり、ロール 3

4.

SWETグループとは (2/2) - テスト自動化の支援(アドバイザー) - テスト/検証ツール作成 - CI/CD、デバイスファーム - 形式手法、テスト技術などのR&D 4

5.

本日お話すること 5

6.

CEDEC 2019 『組織にテストを書く文化を根付か せる戦略と戦術』

7.

『組織にテストをー』で語られていること - 開発者がテストコード(ユニットテスト)を書く 必要性 - テストが開発効率を上げるという根拠 - どこから取り掛かるべきかの指針 本セッションの前提となる知見が詰まっています まだ見ていない方はぜひCEDiLで! 7

8.

でも、ゲーム開発は特殊だから… (ありがちな感想) 8

9.

ゲーム開発は特殊なのか? - 特殊であることは否定しませんが、他の分野 もだいたいそれぞれ特殊 - ゲームの種類、アーキテクチャによっても事情 は異なる 9

10.

ゲームでもテストを書くことが有効な具体例や ベストプラクティスを、本セッションで紹介します! 10

11.

アジェンダ - 「テスト」に関する4つの誤解 - Unity Test Runner を使ってみよう - ゲーム開発向け ユニットテストパターン - ”テストを書く文化を根付かせる” 試み - Unity Test Runner Tips 11

12.

「テスト」に関する 4つの誤解 12

13.

誤解 1 「テスト」 == 「デバッグ」 13

14.

「デバッグ」と「テスト」 - 主にゲーム開発で使われる「デバッグ」 - バグの発見 - 原因箇所の特定 - バグの修正 14

15.

「デバッグ」と「テスト」 - ソフトウェア開発全般で言う「テスト」の目的 - バグの発見 - 対象ソフトウェアの品質レベルが十分であ ることを確認する - バグの作りこみを防ぐ “ISTQB The Certified Tester Foundation Level in Software Testing Syllabus”より 抜粋・意訳 15

16.

「デバッグ」と「テスト」 - ソフトウェア開発全般で言う「テスト」の目的 - バグの発見 - 対象ソフトウェアの品質レベルが十分であ ることを確認する - バグの作りこみを防ぐ “ISTQB The Certified Tester Foundation Level in Software Testing Syllabus”より 抜粋・意訳 16

17.

「デバッグ」と「テスト」 - ソフトウェア開発全般で言う「テスト」の目的 - バグの発見 - 対象ソフトウェアの品質レベルが十分であ ることを確認する - バグの作りこみを防ぐ “ISTQB The Certified Tester Foundation Level in Software Testing Syllabus”より 抜粋・意訳 17

18.

対象ソフトウェアの品質レベルが十分であることを確認する - 正しく動くことを確認する - “品質を測る” 18

19.

「デバッグ」と「テスト」 - ソフトウェア開発全般で言う「テスト」の目的 - バグの発見 - 対象ソフトウェアの品質レベルが十分であ ることを確認する - バグの作りこみを防ぐ “ISTQB The Certified Tester Foundation Level in Software Testing Syllabus”より 抜粋・意訳 19

20.

バグの作り込みを防ぐ - リグレッション(デグレ、エンバグ) - 発見は早ければ早いほどよい 20

21.

誤解 2 ビルドしたゲームを手で 操作するのがテスト 21

22.

結合度の低いレベルで行なうテスト - 小さな単位で早期に確実に検証する - ユニットテスト、インテグレーションテスト - 手では操作できないので、自動テスト - Unity Test Runner - NUnit 22

23.

ユニットテストの利点 - 素早く実行して、バグを早期に発見できる - 個々の部品の品質を上げておく - 再現の難しい条件を作り出しやすい - タイミング、乱数、GUIで指定できない値 23

24.

誤解 3 テストを書けば品質が上がる 24

25.

テストを書くだけでは品質は上がらない - テストは ”品質を測る” だけ - そもそも、品質の低いプロダクトにはテストが 書きにくい 25

26.

テスタビリティ(テスト性) - テストのしやすさ(書きやすさ)についての 品質特性 - テストしやすいコードは、その時点でバグも少 なく可読性も高い 26

27.

ルンバビリティ ※ルンバは、アイロボット コーポレイションの登録商標です 27

28.

内部品質 - 外からのテストではわからない、コードの品質 - テスタビリティ(テスト性) - 保守性(可読性など) - 移植性 28

29.

誤解 4 テストコードは、プロダクトを 開発した後から書く 29

30.

テストを書くタイミング - プロダクトを開発しながらテストコードも書き、 実行する - テストしやすいプロダクトコードになる - バグが見つかるまでの時間が短くなる 30

31.

テストコードは、建築における”足場” 31

32.

Unity Test Runner を 使ってみよう 32

33.

Unity Test Runner とは - Unity標準のユニットテスト実行環境 - Unity 2019.2 からはPackage化 - Unity Test Framework (UTF) - NUnit 3.0 ベースのテストコード 33

34.

Unity Test Runner の実行環境 - Unityエディタで実行 - EditMode / PlayMode / Player(実機) - Test Runner ウィンドウ / CLI - JetBrains Rider で実行 - EditModeのみ 34

35.

Unity Test Runner ウィンドウ (1/3) - Unityエディタの メニューから Window > General > Test Runner 35

36.

Unity Test Runner ウィンドウ (2/3) - EditMode か PlayMode を選択して “Run All” 36

37.

Unity Test Runner ウィンドウ (3/3) - EditMode か PlayMode を選択して “Run All” - 成否が表示される 37

38.

EditMode と PlayMode - EditMode - Unityエディタ上で素早くテスト実行できる - PlayMode - Unityエディタのプレイモードで実行できる - 様々な注意事項(後述) 38

39.

EditMode Tests (1/3) - テストコードの置き場所は2通り - Editorフォルダ - 任意のフォルダに Assembly Definition File を配置してEditorアセンブリにする 39

40.

EditMode Tests (2/3) - Assembly Definition File 設定内容 - テスト対象のアセンブリ への参照 - Test Assemblies: on - Editor: on 40

41.

EditMode Tests (3/3) - テストクラスに制約は無いが、テスト対象の粒 度に合わせるのが慣例 - メソッドに [Test] アトリビュートをつけたもの がテストメソッドと認識される 41

42.
[beta]
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

43.

EditMode Tests の例 - テストメソッド using NUnit.Framework; using UnityEngine; public class BulletTest { [Test] public void IsHit_notHit() { } } 43

44.

EditMode Tests の例 - Verify using NUnit.Framework; using UnityEngine; public class BulletTest { [Test] public void IsHit_notHit() { Assert.That(actual, Is.False); } } 44

45.

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

46.

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

47.

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

48.

[UnityTest] アトリビュート - 複数フレームにまたがるテストを記述できる - EditModeでは `EditorApplication.update` コールバックループで実行 - yield return には null しか指定できない - PlayModeではコルーチンで実行 48

49.

PlayMode Tests - EditMode Testsとは別のア センブリ - テスト対象のアセンブリへの 参照 - Test Assemblies: on - Editor: off 49

50.

PlayMode Tests の注意事項 (1/3) - テスト用の空のSceneファイルが生成・ロード される - テスト実行ごとのオーバーヘッド - UnityエディタがクラッシュするとScene ファイルが残ってしまう(まれによくある) 50

51.

PlayMode Tests の注意事項 (2/3) - 一連のテスト実行の間、生成されたSceneは 使い回される - 適切にクリーンナップしていないと、後続の テストに影響 51

52.

PlayMode Tests の注意事項 (3/3) - Sceneベースのテストを書くのはつらい - インテグレーションテストを書くのであれば、 PocoやAltUnityTesterを検討すべき 52

53.

ゲーム開発向け ユニットテストパターン 53

54.

テストの基本 54

55.

テストの基本は「入力」と「出力」 入力 テスト 対象 55 出力

56.

「入力」と「出力」の見極め - 「セーブしました」メッセージが出たからOKな のか? - クラッシュしなかったからOKなのか? - 適切なAssertが書けているか? - もととなるのは「テスト観点」 56

57.

観点 - 観点によって、入力と出力の捉え方が変わる - 例えば「実行速度」が観点のときは? 57

58.

価値の高いテストを書く 58

59.

価値の高いテスト - リスクが高いところ - クラッシュ、ショーストッパー - 課金まわり - 見落としがちなところ - あまり通らないルート、画面 59

60.

重要でないテスト - 副作用的なもの、目で見てわかるもの - エフェクト、SE 60

61.

組み合わせ条件を減らす 61

62.

組み合わせ条件が多すぎるケース - 要素が多ければテストも複雑になる - 当たり判定、ダメージ、破壊判定を行なう - 入力:敵の座標とコライダ、HP、弾の座標と コライダ、威力、… 62

63.

責務を分ける - プロダクトコード側の責務を適切に分割する ことで、個々のコンポーネントが扱う要素は減 る - 当たり判定を行うメソッド - ダメージ・破壊判定を行うメソッド 63

64.

責務を分けたらコールスタックが増えて 性能が出ないのでは? 64

65.

遅くなるのでは? - 本当にボトルネックになるところは少ない - 本当にボトルネックになる場合 - コンパイラの最適化を信じる - ref - インライン化 65

66.

パフォーマンステスト 66

67.

パフォーマンステスト - ユニットテストの段階から意識する - メソッド単位に実行時間を測定しておく - PlayModeでfpsを測定する 67

68.

パフォーマンステスト - とはいえ、実行環境に依存するのでピーキー にチューニングするものでもない - 魔除けのお守りくらいの感じ - 時間でなく、AllocatingGCMemoryで測る 68

69.

他のオブジェクトへの依存 69

70.

依存オブジェクト - テスト対象が内部で使用している他のオブ ジェクト - テスト結果に影響するもの(間接入力) - 依存オブジェクトに渡した引数を評価したい (間接出力) 70

71.

テストダブル - スタントダブル - 影武者 - 受け身なイメージ? - 勝手なことをしそうなイメージ? - 詳しい定義は『xUnit Test Patterns』を参照 71

72.

テストダブル (1/4) 72

73.

テストダブル (2/4) 73

74.

テストダブル (3/4) 74

75.

テストダブル (4/4) 75

76.

仕様変更のたびにテストが壊れる 76

77.

よくある誤解 - ✗ 仕様変更があるからテストは書けない - ○ 仕様変更があるからテストで保護しておく - YAGNI - 保守しやすいテストコード - 邪魔になったら削除すればいい 77

78.

ありがちなこと - 仕様ではなく、実装のテストを書いてしまう - 実装は複数パターン考えられる - 変更で壊れやすいテストになる 78

79.

副作用は検証しないという選択肢 - 本当に重要な、壊れては困る部分のみ検証す る - 当たり判定、破壊判定 - 副作用は目視に頼る - エフェクト、サウンド 79

80.

あえて定石から外れる - 境界値を攻めすぎない - 条件網羅を追いすぎない 80

81.

テストコードは捨ててもいい - 無理にメンテナンスするより、捨ててしまう - TDDで過剰に書いたものを取り除く 81

82.

テストコードも構造化 - オブジェクトの依存関係が深くなりがち - 似たような初期化処理が増える - 面倒を避けようとテストダブルを多用すると、 変更に弱いテストコードになる 82

83.

“テストコードはガラスのような壊れやすいもの ではなく、竹のようにしなやかで柔軟性の高い ものを目指すべき” Jon Reid 83

84.

テストコードは、建築における”足場” 84

85.

竹で足場を組む なぜ香港の工事現場は、竹で足場を組むのか? https://www.itmedia.co.jp/makoto/articles/0811/14/news049.html 85

86.

”組織にテストを書く文化を 根付かせる” 試み 86

87.

SWETグループとは(再掲) - Software Engineer in Test の略 - Google: SET, Microsoft: SDET - 他の横断的組織と連携し、プロダクト開発を サポート - 組織の名前であり、ロールでもある 87

88.

SWETの採用したアプローチ - 共通的フレームワークのリファレンス実装に 対し、テストコードのサンプルを書く - バグを摘出(自動テストで新種のバグが見つ かるのは稀です) - ほかへの引き合いにつながった 88

89.

タイミングがよかった 89

90.

「ボトムアップではじめる」のは難しい - どの箇所でも効率よくテストが書けるわけで はない - 無理に書いたテストはよくないテストだったり、 ROIが低かったり 90

91.

Semper Paratus “常に備えよ” - 米沿岸警備隊の格言 91

92.

Unity Test Runner Tips 92

93.

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

94.

例外の発生を確認するテスト Assert.That( () => ExceptionSample.ThrowIndexOutOfRangeException(), Throws.TypeOf<NullReferenceException>()); NullReferenceExceptionが発生したらsuccess 94

95.

パラメタライズドテスト [Test] public void ParameterizedSampleTest( [Values(100, 200, 300)] int a) { var actual = sut(a); // snip } Valuesで指定したパターンが実行される 95

96.

パラメタライズド・組み合わせテスト [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

97.

[Ignore] アトリビュート - なんらかの理由(テスト対象が未実装など)で 実行対象から除外したいとき - [Ignore(“comment“)] でコメントも書ける 97

98.

internal メソッドのテスト - Editor下は別アセンブリになるので、テスト対象の internalメソッドを呼べない - テスト対象アセンブリ内で [assembly: InternalsVisibleTo(“Assembly-CSharpEditor”)] を宣言することで呼べるようになる 98

99.

private メソッドのテスト - 原則、テストコードからアクセスできない - リフレクションという手段はあるが避けるべき (とても壊れやすいテストになる) - 適切にクラスが分割・委譲されていれば困ら ないはず 99

100.

ブログでも情報発信していきます - DeNA Testing Blog (SWET) https://swet.dena.com/ - 個人ブログ https://www.nowsprinting.com/ 100