Unity Unit Test 單元測試(2)


原文地址:1. Unity3D 單元測試框架介紹

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的單元測試框架就先介紹到這里,后面結合實際的項目再做介紹。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM