Unity3D 簡要介紹
如果是游戲行業的同學估計就沒有不知道 Unity3D 的,騰訊的王者榮耀就是基於 Unity3D(簡稱:U3D)來開發的。在 U3D 中,有三個基本的概念:游戲場景(Scene)、游戲物體(GameObject)和組件(Component)。我們可以借用電影來理解這三個概念,整部電影從開始到結束,由很多場景組成,比如動作電影中的打斗場景在廢舊的工廠中進行,廢舊的工廠就是游戲場景;廢舊工廠中的打斗場景中又由很多的物體組成,比如人、荒廢的車床、被嚇飛的白鴿等,這些就是游戲物體;廢棄的車床又由輪子、各個鋼鐵部件組成,這里的輪子和部件就是游戲物體的組件。換成 U3D 中的術語就如下圖所示:

以上就是 U3D 的簡要介紹,如果對 U3D 有興趣但自己又沒有基礎的,可以去 SiKi學院 上找免費的入門視頻來學習,想要看書的話可以去看看《Unity 5.X 從入門到精通》。
Unity3D 開發環境簡要說明
U3D 的開發工具叫 Unity 編輯器,在 下載地址 里選擇對應的版本和平台下的 Unity 編輯器即可,比如我這里選擇 Windows 平台的最新版本,如截圖所示:

U3D 目前只推薦使用 C# 作為腳本開發語言,為了方便編寫和調試代碼通常不直接使用 Unity 編輯器作為 IDE,我們使用 Microsoft Visual Studio(簡稱:VS)作為 C# 腳本的開發工具,可以點擊 下載鏈接 下載 VS,具體的安裝不再詳述。
安裝完成 VS 后,打開 Unity 編輯器,在菜單欄上選擇 Edit > Preferences…,在彈出的 Unity Preferences 對話框中選擇 External Tools > External Script Editor,最后選擇 VS 即可,如下圖所示:

到此為止開發環境配置完成。
基於 Unity 編輯器的測試框架介紹
NUnit.Framework 介紹
在 Unity 編輯器中已經集成了單元測試框架 NUnit,關於 NUnit 可以 點擊鏈接 了解更多,下面基於一個例子對它進行基本的介紹。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
using UnityEngine; //基於 Unity 引擎,必須引用 using NUnit.Framework; //引用NUnit測試框架
[TestFixture, Description("測試套")] //一個類對應一個測試套,通常一個測試特性對應一個測試套。 public class UnitTestDemoTest { [OneTimeSetUp] //在執行該測試套時首先會執行該函數,在整個測試套中只執行一次。 public void OneTimeSetUp() { Debug.Log("OneTimeSetUp"); }
[OneTimeTearDown] //在執行該測試套時最后會執行該函數,在整個測試套中只執行一次。 public void OneTimeTearDown() { Debug.Log("OneTimeTearDown"); }
[SetUp] public void SetUp() //在執行每個用例之前都會執行一次該函數 { Debug.Log("SetUp"); }
[TearDown] //在執行完每個用例之后都會執行一次該函數 public void TearDown() { Debug.Log("TearDown"); }
[TestCase, Description("測試用例1")] //這個函數內部寫測試用例 public void TestCase1() { Debug.Log("TestCase1"); }
[TestCase, Description("測試用例2")] //這個函數內部寫測試用例 public void TestCase2() { Debug.Log("TestCase2"); } }
|
以上是NUnit的一個例子,我們在 Unity 編輯器上執行看下效果。

可以看到,執行的情況就如代碼注釋里的說明一樣。通常,對於測試用例執行需要的必備條件的代碼可以寫在 OneTimeSetUp 里面,比如啟動測試環境;對於測試用例執行完最后需要的清理工作可以寫在 OneTimeTearDown 里面,比如退出測試環境;每個測試用例都需要初始化的公共代碼寫在 SetUp 里面;跑完每個測試用例都需要清理的公共代碼寫在 TearDown 里面。
Unity3D 單元測試的兩種模式
打開 Unity 編輯器,在菜單欄依次選擇 Window > Test Runner,在彈出的對話中可以看到 PlayMode 和 EditMode,這里的 Test Runner 對話框就是執行單元測試的 UI 界面,如果想進一步了解可以點擊 Test Runner 官網介紹 進行深入了解。又或者在 Project 視圖下依次執行 按下鼠標右鍵 > Create > Testing 也可以看到有 PlayMode 和 EditMode 字眼,下面是關於它兩的截圖。


EditMode 測試對於 Unity 編輯器而言,就是指在編輯狀態下去測試,而 PlayMode 測試對於 Unity 編輯器而言,就是指在 Unity 運行時的測試。我們可以這么理解,EditMode 是代碼的靜態測試,測試時不需要被測代碼跑起來,其實這里的 EditMode 就是跟其他編程語言的單元測試是一個意思;相對來說,PlayMode 就是代碼的動態測試,被測代碼需要跑起來,這時的代碼環境跟業務場景結合起來。
EditMode 測試模式
上文提到 EditMode 就是傳統意義上的單元測試,這里結合個例子介紹下。下面的代碼是被測代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
//被測代碼 public class BuildManager : MonoBehaviour { public Text moneyText; public int money = 1500; ...... //被測試函數 public void ChangeMoney(int change = 0) { money += change; moneyText.text = "¥" + money; } ...... }
|
在編寫 EditMode 模式的測試代碼時有一點需要注意下,測試代碼需要放在以 Editor 命名的文件夾下(子文件下也行,反正得在 Editor 下)才行,不然 Unity 編輯器無法識別。用例應該放在如下圖所示的地方:

然后編寫測試 BuildManager 對象的用例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
using UnityEngine; using NUnit.Framework; using UnityEditor.SceneManagement; //加載場景,實例化被測腳本時需要
[TestFixture] public class BuildManagerTest : BuildManager { private BuildManager buildManager;
[OneTimeSetUp] //打開場景,目的是獲取被測對象 BuildManager 實例 public void OneTimeSetUp() { EditorSceneManager.OpenScene("Assets/Scenes/MainScene.unity"); buildManager = GameObject.Find("GameManager").GetComponent<BuildManager>(); }
[OneTimeTearDown] //測試完成后銷毀測試對象 public void OneTimeTearDown() { buildManager = null; }
[SetUp] //初始化被測對象的屬性值 public void SetUp() { buildManager.money = 1500; }
[TearDown] //恢復被測對象的屬性值 public void TearDown() { buildManager.money = 1500; }
[TestCase] //測試被測對象的函數邏輯 public void AddMoneyTest() {; buildManager.ChangeMoney(100); Assert.AreEqual(1600, buildManager.money); } [TestCase] //測試被測對象的函數邏輯 public void SubMoneyTest() { buildManager.ChangeMoney(-1000); Assert.AreEqual(500, buildManager.money); } }
|
打開 Test Runner 對話框,選中測試用例 AddMoneyTest() 和 SubMoneyTest(),然后點擊 Run Selected 即可。如下圖所示:

PlayMode 測試模式
如果之前沒有創建過 PlayMode 模式下的測試用例,那么打開 Test Runner 對話框,並切換到 PlayMode 頁簽下,你會看到如下圖所示的提示:

然后點擊 Enable playmode tests 按鈕,再點擊 Enable 確定按鈕。

繼續點擊 OK 按鈕,接着點擊 Create Playmode Test with methods 按鈕,發現創建了一測試腳本:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
using UnityEngine; using UnityEngine.TestTools; using NUnit.Framework; using System.Collections;
public class NewPlayModeTest {
[Test] public void NewPlayModeTestSimplePasses() { // Use the Assert class to test conditions. }
// A UnityTest behaves like a coroutine in PlayMode // and allows you to yield null to skip a frame in EditMode [UnityTest] public IEnumerator NewPlayModeTestWithEnumeratorPasses() { // Use the Assert class to test conditions. // yield to skip a frame yield return null; } }
|
這里重點介紹下 PlayMode 模式下測試用例的用法,如上代碼所示,其實 [test] 的注解就是普通的測試標簽,[UnityTest]標簽才是 PlayMode 測試用例的標簽,同時該注解下的函數返回類型是個迭代器 IEnumerator,我們注意到該函數內部還有一條語句 yield return null。其實還有類似的寫法,如 yield return new WaitForSeconds()、 yield return new WaitForEndOfFrame()、 yield return new WaitForFixedUpdate() 等。如果學過 Python 我們知道在函數內部中多了 yield 語句它就是生成器,生成器不會一下子返回可迭代對象的所有數據,而是每次返回一條數據,直至迭代完成數據。這里的 yield return 語句有點類似的意思。我們之前說過,PlayMode 測試模式是在代碼運行中去測試的,在 U3D 中運行的場景、物體或組件(代碼腳本也是一種組件)是每一幀每一幀持續去刷新的,可以把每一幀理解成一張圖片,隨着時間每一幀每一幀的刷新就形成了視頻動畫的效果。說回這里的 yield return的作用,它可以等待運行中的場景物體刷新一段時間(可以是等一幀、等一秒等)后再繼續執行下面的測試代碼。這樣的好處就是可以隨着時間(幀不斷刷新)來操作不同時期的場景、獲取不同時期的場景物體信息等,來實現模擬用戶或模擬場景的測試。
下面結合Demo來具體介紹下,先說說被系統:敵人(球)會在指定的路線下跑,跑到終點敵人就贏。玩家可以在指定的位置放置炮台,只要敵人走到攻擊范圍內,炮台就射擊。現在用 PlayMode 模式簡單測試下炮台的子彈是否會移動。下面是測試的代碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
using UnityEngine; using UnityEngine.TestTools; using NUnit.Framework; using System.Collections;
public class playmodetest { Waypoints waypoints; Bullet bullet; Enemy target; Vector3 initPosition; [SetUp] //初始化測試環境,然后獲取子彈的初始位置 public void SetUp() { GameObject waypointsGo = new GameObject("Waypoints"); waypoints = waypointsGo.AddComponent<Waypoints>(); GameObject bulletGo = new GameObject("bullet"); bullet = bulletGo.AddComponent<Bullet>(); initPosition = bullet.transform.position; GameObject Enemy1 = Resources.Load<GameObject>("Prefab/Enemy1/Enemy1"); Enemy enemy = Enemy1.GetComponent<Enemy>(); target = GameObject.Instantiate(enemy, enemy.transform.position, enemy.transform.rotation); bullet.SetTarget(target.transform); }
[UnityTest] //等待下一幀,然后比較子彈的當前位置是否與初始位置一致 public IEnumerator CheckBullet() { yield return null; Assert.AreNotEqual(initPosition, bullet.transform.position); } }
|
測試結果顯示測試通過:


好了,U3D的單元測試框架就先介紹到這里,后面結合實際的項目再做介紹。