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的单元测试框架就先介绍到这里,后面结合实际的项目再做介绍。