Unity Test Runner を活用して
内部品質を向上しよう
株式会社ディー・エヌ・エー
長谷川 孝二
自己紹介
2
- 長谷川 孝二
- DeNA SWETグループ

2019.2 〜
- 著書『iOSアプリテスト自動
化入門』など
- Unityへの入り口はVR
※アイコンはイメージです
SWETグループとは (1/2)
3
- Software Engineer in Test の略
- Google: SET, Microsoft: SDET
- 他の横断的組織と連携し、プロダクト開発を
サポート
- 組織の名前であり、ロール
SWETグループとは (2/2)
4
- テスト自動化の支援(アドバイザー)
- テスト/検証ツール作成
- CI/CD、デバイスファーム
- 形式手法、テスト技術などのR&D
5
本日お話すること
CEDEC 2019 『組織にテストを書く文化を根付か
せる戦略と戦術』
『組織にテストをー』で語られていること
7
- 開発者がテストコード(ユニットテスト)を書く
必要性
- テストが開発効率を上げるという根拠
- どこから取り掛かるべきかの指針
本セッションの前提となる知見が詰まっています
まだ見ていない方はぜひCEDiLで!
8
でも、ゲーム開発は特殊だから…
(ありがちな感想)
ゲーム開発は特殊なのか?
9
- 特殊であることは否定しませんが、他の分野
もだいたいそれぞれ特殊
- ゲームの種類、アーキテクチャによっても事情
は異なる
10
ゲームでもテストを書くことが有効な具体例や
ベストプラクティスを、本セッションで紹介します!
アジェンダ
11
- 「テスト」に関する4つの誤解
- Unity Test Runner を使ってみよう
- ゲーム開発向け ユニットテストパターン
- ”テストを書く文化を根付かせる” 試み
- Unity Test Runner Tips
「テスト」に関する
4つの誤解
12
13
「テスト」 == 「デバッグ」
誤解 1
「デバッグ」と「テスト」
14
- 主にゲーム開発で使われる「デバッグ」
- バグの発見
- 原因箇所の特定
- バグの修正
「デバッグ」と「テスト」
15
- ソフトウェア開発全般で言う「テスト」の目的
- バグの発見
- 対象ソフトウェアの品質レベルが十分であ
ることを確認する
- バグの作りこみを防ぐ
“ISTQB The Certified Tester Foundation Level in Software Testing Syllabus”より 抜粋・意訳
「デバッグ」と「テスト」
16
- ソフトウェア開発全般で言う「テスト」の目的
- バグの発見
- 対象ソフトウェアの品質レベルが十分であ
ることを確認する
- バグの作りこみを防ぐ
“ISTQB The Certified Tester Foundation Level in Software Testing Syllabus”より 抜粋・意訳
「デバッグ」と「テスト」
17
- ソフトウェア開発全般で言う「テスト」の目的
- バグの発見
- 対象ソフトウェアの品質レベルが十分であ
ることを確認する
- バグの作りこみを防ぐ
“ISTQB The Certified Tester Foundation Level in Software Testing Syllabus”より 抜粋・意訳
対象ソフトウェアの品質レベルが十分であることを確認する
18
- 正しく動くことを確認する
- “品質を測る”
「デバッグ」と「テスト」
19
- ソフトウェア開発全般で言う「テスト」の目的
- バグの発見
- 対象ソフトウェアの品質レベルが十分であ
ることを確認する
- バグの作りこみを防ぐ
“ISTQB The Certified Tester Foundation Level in Software Testing Syllabus”より 抜粋・意訳
バグの作り込みを防ぐ
20
- リグレッション(デグレ、エンバグ)
- 発見は早ければ早いほどよい
21
ビルドしたゲームを手で
操作するのがテスト
誤解 2
結合度の低いレベルで行なうテスト
22
- 小さな単位で早期に確実に検証する
- ユニットテスト、インテグレーションテスト
- 手では操作できないので、自動テスト
- Unity Test Runner
- NUnit
ユニットテストの利点
23
- 素早く実行して、バグを早期に発見できる
- 個々の部品の品質を上げておく
- 再現の難しい条件を作り出しやすい
- タイミング、乱数、GUIで指定できない値
24
テストを書けば品質が上がる
誤解 3
テストを書くだけでは品質は上がらない
25
- テストは ”品質を測る” だけ
- そもそも、品質の低いプロダクトにはテストが
書きにくい
テスタビリティ(テスト性)
26
- テストのしやすさ(書きやすさ)についての

品質特性
- テストしやすいコードは、その時点でバグも少
なく可読性も高い
ルンバビリティ
27
※ルンバは、アイロボット コーポレイションの登録商標です
内部品質
28
- 外からのテストではわからない、コードの品質
- テスタビリティ(テスト性)
- 保守性(可読性など)
- 移植性
29
テストコードは、プロダクトを
開発した後から書く
誤解 4
テストを書くタイミング
30
- プロダクトを開発しながらテストコードも書き、
実行する
- テストしやすいプロダクトコードになる
- バグが見つかるまでの時間が短くなる
31
テストコードは、建築における”足場”
32
Unity Test Runner を
使ってみよう
Unity Test Runner とは
33
- Unity標準のユニットテスト実行環境
- Unity 2019.2 からはPackage化
- Unity Test Framework (UTF)
- NUnit 3.0 ベースのテストコード
Unity Test Runner の実行環境
34
- Unityエディタで実行
- EditMode / PlayMode / Player(実機)
- Test Runner ウィンドウ / CLI
- JetBrains Rider で実行
- EditModeのみ
Unity Test Runner ウィンドウ (1/3)
35
- Unityエディタの
メニューから

Window >
General > 

Test Runner
Unity Test Runner ウィンドウ (2/3)
36
- EditMode か PlayMode
を選択して “Run All”
Unity Test Runner ウィンドウ (3/3)
37
- EditMode か PlayMode
を選択して “Run All”
- 成否が表示される
EditMode と PlayMode
38
- EditMode
- Unityエディタ上で素早くテスト実行できる
- PlayMode
- Unityエディタのプレイモードで実行できる
- 様々な注意事項(後述)
EditMode Tests (1/3)
39
- テストコードの置き場所は2通り
- Editorフォルダ
- 任意のフォルダに Assembly Definition
File を配置してEditorアセンブリにする
EditMode Tests (2/3)
40
- Assembly Definition File
設定内容
- テスト対象のアセンブリ
への参照
- Test Assemblies: on
- Editor: on
EditMode Tests (3/3)
41
- テストクラスに制約は無いが、テスト対象の粒
度に合わせるのが慣例
- メソッドに [Test] アトリビュートをつけたもの
がテストメソッドと認識される
EditMode Tests の例 - テスト対象
42
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
}
43
EditMode Tests の例 - テストメソッド
using NUnit.Framework;
using UnityEngine;
public class BulletTest
{
[Test]
public void IsHit_notHit()
{
}
}
44
EditMode Tests の例 - Verify
using NUnit.Framework;
using UnityEngine;
public class BulletTest
{
[Test]
public void IsHit_notHit()
{
Assert.That(actual, Is.False);
}
}
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);
}
}
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);
}
}
47
Assert のバリエーション
Assert.That(actual,	Is.True);	
Assert.That(actual,	Is.EqualTo(200));	
Assert.That(actual,	Is.GreaterThan(100));	
Assert.That(actual,	Is.LessThanOrEqualTo(300));	
Assert.That(actual,	Is.InRange(100,	500));	
Assert.AreEqual(200,	actual);
[UnityTest] アトリビュート
48
- 複数フレームにまたがるテストを記述できる
- EditModeでは `EditorApplication.update`
コールバックループで実行
- yield return には null しか指定できない
- PlayModeではコルーチンで実行
PlayMode Tests
49
- EditMode Testsとは別のア
センブリ
- テスト対象のアセンブリへの
参照
- Test Assemblies: on
- Editor: off
PlayMode Tests の注意事項 (1/3)
50
- テスト用の空のSceneファイルが生成・ロード
される
- テスト実行ごとのオーバーヘッド
- UnityエディタがクラッシュするとScene
ファイルが残ってしまう(まれによくある)
PlayMode Tests の注意事項 (2/3)
51
- 一連のテスト実行の間、生成されたSceneは
使い回される
- 適切にクリーンナップしていないと、後続の
テストに影響
PlayMode Tests の注意事項 (3/3)
52
- Sceneベースのテストを書くのはつらい
- インテグレーションテストを書くのであれば、
PocoやAltUnityTesterを検討すべき
53
ゲーム開発向け
ユニットテストパターン
54
テストの基本
55
テストの基本は「入力」と「出力」
テスト
対象
出力入力
56
「入力」と「出力」の見極め
- 「セーブしました」メッセージが出たからOKな
のか?
- クラッシュしなかったからOKなのか?
- 適切なAssertが書けているか?
- もととなるのは「テスト観点」
57
観点
- 観点によって、入力と出力の捉え方が変わる
- 例えば「実行速度」が観点のときは?
58
価値の高いテストを書く
59
価値の高いテスト
- リスクが高いところ
- クラッシュ、ショーストッパー
- 課金まわり
- 見落としがちなところ
- あまり通らないルート、画面
60
重要でないテスト
- 副作用的なもの、目で見てわかるもの
- エフェクト、SE
61
組み合わせ条件を減らす
62
組み合わせ条件が多すぎるケース
- 要素が多ければテストも複雑になる
- 当たり判定、ダメージ、破壊判定を行なう
- 入力:敵の座標とコライダ、HP、弾の座標と
コライダ、威力、…
63
責務を分ける
- プロダクトコード側の責務を適切に分割する
ことで、個々のコンポーネントが扱う要素は減
る
- 当たり判定を行うメソッド
- ダメージ・破壊判定を行うメソッド
64
責務を分けたらコールスタックが増えて
性能が出ないのでは?
65
遅くなるのでは?
- 本当にボトルネックになるところは少ない
- 本当にボトルネックになる場合
- コンパイラの最適化を信じる
- ref
- インライン化
66
パフォーマンステスト
67
パフォーマンステスト
- ユニットテストの段階から意識する
- メソッド単位に実行時間を測定しておく
- PlayModeでfpsを測定する
68
パフォーマンステスト
- とはいえ、実行環境に依存するのでピーキー
にチューニングするものでもない
- 魔除けのお守りくらいの感じ
- 時間でなく、AllocatingGCMemoryで測る
69
他のオブジェクトへの依存
70
依存オブジェクト
- テスト対象が内部で使用している他のオブ
ジェクト
- テスト結果に影響するもの(間接入力)
- 依存オブジェクトに渡した引数を評価したい
(間接出力)
71
テストダブル
- スタントダブル
- 影武者
- 受け身なイメージ?
- 勝手なことをしそうなイメージ?
- 詳しい定義は『xUnit Test Patterns』を参照
72
テストダブル (1/4)
73
テストダブル (2/4)
74
テストダブル (3/4)
75
テストダブル (4/4)
76
仕様変更のたびにテストが壊れる
77
よくある誤解
- ✗ 仕様変更があるからテストは書けない
- ○ 仕様変更があるからテストで保護しておく
- YAGNI
- 保守しやすいテストコード
- 邪魔になったら削除すればいい
78
ありがちなこと
- 仕様ではなく、実装のテストを書いてしまう
- 実装は複数パターン考えられる
- 変更で壊れやすいテストになる
79
副作用は検証しないという選択肢
- 本当に重要な、壊れては困る部分のみ検証す
る
- 当たり判定、破壊判定
- 副作用は目視に頼る
- エフェクト、サウンド
80
あえて定石から外れる
- 境界値を攻めすぎない
- 条件網羅を追いすぎない
81
テストコードは捨ててもいい
- 無理にメンテナンスするより、捨ててしまう
- TDDで過剰に書いたものを取り除く
82
テストコードも構造化
- オブジェクトの依存関係が深くなりがち
- 似たような初期化処理が増える
- 面倒を避けようとテストダブルを多用すると、
変更に弱いテストコードになる
83
“テストコードはガラスのような壊れやすいもの
ではなく、竹のようにしなやかで柔軟性の高い
ものを目指すべき”
Jon Reid
84
テストコードは、建築における”足場”
85
なぜ香港の工事現場は、竹で足場を組むのか?
https://www.itmedia.co.jp/makoto/articles/0811/14/news049.html
竹で足場を組む
”組織にテストを書く文化を
根付かせる” 試み
86
SWETグループとは(再掲)
87
- Software Engineer in Test の略
- Google: SET, Microsoft: SDET
- 他の横断的組織と連携し、プロダクト開発を
サポート
- 組織の名前であり、ロールでもある
SWETの採用したアプローチ
88
- 共通的フレームワークのリファレンス実装に
対し、テストコードのサンプルを書く
- バグを摘出(自動テストで新種のバグが見つ
かるのは稀です)
- ほかへの引き合いにつながった
89
タイミングがよかった
「ボトムアップではじめる」のは難しい
90
- どの箇所でも効率よくテストが書けるわけで
はない
- 無理に書いたテストはよくないテストだったり、
ROIが低かったり
91
Semper Paratus
“常に備えよ”
- 米沿岸警備隊の格言
92
Unity Test Runner
Tips
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 があります
94
例外の発生を確認するテスト
Assert.That(	
		()	=>	ExceptionSample.ThrowIndexOutOfRangeException(),	
		Throws.TypeOf<NullReferenceException>());
NullReferenceExceptionが発生したらsuccess
95
パラメタライズドテスト
[Test]	
public	void	ParameterizedSampleTest(	
		[Values(100,	200,	300)]	int	a)	
{	
				var	actual	=	sut(a);	
				//	snip	
}
Valuesで指定したパターンが実行される
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]指定の総当たり
97
[Ignore] アトリビュート
- なんらかの理由(テスト対象が未実装など)で
実行対象から除外したいとき
- [Ignore(“comment“)] でコメントも書ける
98
internal メソッドのテスト
- Editor下は別アセンブリになるので、テスト対象の
internalメソッドを呼べない
- テスト対象アセンブリ内で

[assembly: InternalsVisibleTo(“Assembly-CSharp-
Editor”)]

を宣言することで呼べるようになる
99
private メソッドのテスト
- 原則、テストコードからアクセスできない
- リフレクションという手段はあるが避けるべき
(とても壊れやすいテストになる)
- 適切にクラスが分割・委譲されていれば困ら
ないはず
100
ブログでも情報発信していきます
- DeNA Testing Blog (SWET)

https://swet.dena.com/
- 個人ブログ

https://www.nowsprinting.com/

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