一、单元测试的定义与作用

  单元测试定义:单元测试在传统软件开发中是非常重要的工具,它是指对软件中的最小可测试单元进行检查和验证,一般情况下就是对代码中的一个函数去进行验证,检查它的正确性。一个单元测试是一段自动化的代码,这段代码调用被测试的工作单元,之后对这个单元的单个最终结果的某些假设进行检验。单元测试使用单元测试框架编写,并要求单元测试可靠、可读并且可维护。只要产品代码不发生变化,单元测试的结果是稳定的。(百度的)

  单元测试可以让你在软件开发的早期阶段发现 Bug,而不必到集成测试的时候才发现,开发完成一个模块(类、函数)就对应地做一个单元测试,尽早发现并处理掉bug,提高代码的质量。(反正单元测试就是杠杠好!)

二、在Unity中使用NUnit进行单元测试

  话说,马三在工作的过程中,极少地发现周围的同事会对自己编写功能进行单元测试。一般都是开发完功能以后,随便写两段测试的代码(有的甚至都不测一下),一看没有问题就丢到SVN或者Git仓库里面了。结果当游戏出包以后,测试团队总会反馈回很多完全可以提前规避掉的低级bug。当然可能他们本身没有单元测试的习惯或者由于活多、工期太紧等种种原因,才不做单元测试的。(活都要干不完了,还做毛测试,Delay不扣钱啊!?)

  好了,闲话扯完该说说咱们这个单元测试了。单元测试目前有很多成熟的框架可以供我们使用,我比较推荐的就是Unity Editor自带的Editor Tests Runner,功能不多,但是已经够用了,使用也很方便。Editor Tests Runner是开源单元测试工具NUnit在Unity引擎中的实现,目前Unity中使用的NUnit版本是2.6.4。

  Editor Tests Runner可以通过Window -> Editor Tests Runner菜单打开,它的样子如下图所示:

  

  在这个窗口中显示了当前添加的单元测试用例,以及他们通过的情况。首先,你需要点击窗口左上角的Run All按钮来执行所有的单元测试。绿色的对号表示这个用例通过了单元测试,红色的禁止符号表示未通过单元测试。

  下面我们来看一下如何编写单元测试的代码。单元测试代码和游戏运行时代码是分开保存的,它只在Editor环境下可用,因此你需要把它放到Editor目录下。

  首先为了下面的测试,我们先定义一个自定义类型的错误异常,提前备用。只需要让它直接继承 ApplicationException 就可以了,代码如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; /// <summary>
/// 自定义的异常类型
/// </summary>
class NegativeHealthException : ApplicationException
{ }

  下面编写我们的需要进行被测试的模块或者代码。假设游戏代码中存在一个Player类来代表主角色,里面有几个函数用来在玩家受到伤害时减少血量,或者通过药水回复血量。其中Damage函数写了三个版本,一个是正确的,两个是返回错误结果的。在正确的函数中,当 Health 的值小于 100 的时候,会抛出一个刚才我们自定义的异常。代码如下所示:

 using System.Collections;
using System.Collections.Generic;
using UnityEngine; public class Player{ public float Health { get; set; } #region 正确的方法 public void Damage(float value)
{
Health -= value;
if (Health < )
{
throw new NegativeHealthException();
}
} public void Recover(float value)
{
Health += value;
}
#endregion #region 错误的方法 public void DamageWrong(float value)
{
Health -= value + ;
} public void DamageNoException(float value)
{
Health -= value;
}
#endregion
}

  接着我们来编写单元测试代码。这里我们创建了一个叫做PlayerTest的类,里面写了两个函数分别代表两个测试用例。为了让Unity识别这两个函数是测试用例,我们需要在函数前加上 [Test] 的属性,这样所有带有 [Test] 属性的函数都会成为一个测试用例,代码如下。

 using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine; public class PlayerTest{ [Test]
public void TestHealth()
{
Player player = new Player();
player.Health = 1000.0f; //通过Assert断言来判断这个函数的返回结果是否符合预期
player.Damage();
Assert.AreEqual(,player.Health); player.Recover();
Assert.AreEqual(,player.Health);
} [Test]
[ExpectedException(typeof(NegativeHealthException))]
public void NegativeHealth()
{
Player player = new Player();
player.Health = ; player.Damage();
player.Damage();
} }

  相信大家在写到 [ExpectedException(typeof(NegativeHealthException))] 的时候,VS肯定会报红,提示找不到 ExpectedException 这个标签,这是因为,ExpectedException这个标签是属于VS的单元测试的内容,在 NUnit.Framework 这个命名空间中,因此我们还需要使用  using NUnit.Framework; 来引入VS的单元测试模块。但是如果你会发现这个模块无法引入,VS没有自动补全这个命名空间,就算手动写上了还是提示找不到。这是为什么呢?

  众所周知,Unity的.NET是基于 Mono 的,因为一些原因,导致Mono并不是包含了所有微软原生的.NET库中的内容。也就是说有些你在Winform、WPF等工程中用到的类库并不能完美地在Mono中使用,这也就是为什么会发生上述找不到单元测试的模块的问题。其实这个问题也很好解决,我们只要把 VS 中的单元测试模块的DLL找到(名为 Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll ),手动拷贝到Unity工程中,再在IDE里面引入它就可以使用了。具体的操作步骤如下:

  1.找到VS中的单元测试模块DLL的所在位置,经过在 Stackoverflow 上面查询,我们得知Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll 一般在 C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll 路径下。如下图所示:

  

  2.把这个DLL手动拷贝到Unity的工程中,并在我们的解决方案中引用它。在我们的Unity项目中新建一个名为 “Plugins” 的文件夹,然后把上面的Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll 拷贝到该文件夹下,再重新打开我们的VS解决方案,就可以发现,这个模块已经自动被引用进来了,之后就可以放心地使用单元测试相关的代码了。

  

  

  一般在传统的C#项目中,我们引用某个DLL的时候,都是通过在VS解决方案的引用项目上右键 -> 添加新引用来导入某个DLL,但是在Unity的项目中,我们在引用选项上右键却发现没有这个选项。其实,只要像上述的那样直接把dll 拷贝到 "Plugins"目录下,VS就会自动把DLL引用到我们的项目中了,非常方便。

  在上面的测试函数中,假如我们想测试Damage这个函数是否正常工作,需要使用 Assert.AreEqual 来判断这个函数的返回结果是否与预期的结果一致。如果Assert.AreEqual判断结果是正确的,就会在Tests Runner窗口中用一个绿色的对号表示这个测试通过了,反之就会用红色的禁止符号表示失败。第二个名为 NegativeHealth 测试用例函数,是用来判断判断这个函数有没有正常地抛出异常,如果没有按照预期抛出异常也会被认为是失败的测试用例。如果你需要捕获抛出异常与你的预期值是否一致,还需要在函数前添加另外一个属性 [ExpectedException(typeof(NegativeHealthException))],这样这段代码就会判断抛出的异常是否正确了。通过下图可以看到,我们所编写的两个测试函数用例都通过了,显示为绿色。

  
  这时候大家可能发现了,上面的脚本对应了测试结果中PlayerTest这一部分,另外还有一个PlayerTestWrong的分组并没有出现。这是因为我们可以在Editor目录下添加多个测试脚本,这些测试脚本可以测试同一个模块的代码,也可以同时测试不同模块的代码。下面让我们来看一下PlayerTestWrong的脚本如何编写,它的内容和刚才的测试代码非常相似,只不过调用了返回错误值的函数。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework; class PlayerTestWrong
{
[Test]
public void TestHealthWrong()
{
Player player = new Player();
player.Health = 1000.0f; player.DamageWrong();
Assert.AreEqual(,player.Health); player.DamageWrong();
Assert.AreEqual(,player.Health);
} [Test]
[ExpectedException(typeof (NegativeHealthException))]
public void NegativeHealthNoException()
{
Player player = new Player();
player.Health = ; player.DamageNoException();
player.DamageNoException();
}
}

  Editor Tests Runner的基本使用方法介绍到这里就结束了,下面介绍一个小技巧。如果你想实现全自动的单元测试的话,可能会考虑使用批处理来自动化执行测试,为此Unity也提供了批处理的方式。如果你需要使用这个功能的话,只需要在运行Unity的时候传入以下参数,每个参数的含义请查看 Unity官方文档 ,本篇博客中就不进行介绍了。

  • runEditorTests
  • editorTestsResultFile
  • editorTestsFilter
  • editorTestsCategories
  • editorTestsVerboseLog

三、小结

  对于游戏开发者来说,单元测试可能比较陌生。不过现在随着游戏复杂度的逐渐提升,另外很多有一定规模的公司都会同时开发多个项目。我们会发现其实有很多功能都被封装为通用的工具库。在这种情况下如果我们再不重视代码的质量,就会导致一个Bug可能同时影响多个项目的开发进度。因此我们还是建议在时间允许的情况下,对比较重要的模块,以及重用性比较高的代码增加单元测试。

  最后放上本篇博客中演示的项目源码:

  Github地址:https://github.com/XINCGer/Unity3DTraining/tree/master/Unit4Unity/Editor%20Test%20Runner   欢迎fork!

  引用资料:

https://stackoverflow.com/questions/3293317/where-is-the-microsoft-visualstudio-testtools-unittesting-namespace-on-vs2010

作者:马三小伙儿
出处:http://www.cnblogs.com/msxh/p/7354229.html 
请尊重别人的劳动成果,让分享成为一种美德,欢迎转载。另外,文章在表述和代码方面如有不妥之处,欢迎批评指正。留下你的脚印,欢迎评论!

【Unity游戏开发】浅谈Unity游戏开发中的单元测试的更多相关文章

  1. .NET项目开发—浅谈面向接口编程、可测试性、单元测试、迭代重构(项目小结)

    阅读目录: 1.开篇介绍 2.迭代测试.重构(强制性面向接口编程,要求代码具有可测试性) 2.1.面向接口编程的两个设计误区 2.1.1.接口的依赖倒置 2.1.2.接口对实体的抽象 2.2.迭代单元 ...

  2. 浅谈Unity的渲染优化(1): 性能分析和瓶颈判断(上篇)

    http://www.taidous.com/article-667-1.html 前言 首先,这个系列文章做个大致的介绍,题目"浅谈Unity",因为公司和国内大部分3D手游开发 ...

  3. 浅谈iOS视频开发

     浅谈iOS视频开发 这段时间对视频开发进行了一些了解,在这里和大家分享一下我自己觉得学习步骤和资料,希望对那些对视频感兴趣的朋友有些帮助. 一.iOS系统自带播放器 要了解iOS视频开发,首先我们从 ...

  4. Android开发-浅谈架构(二)

    写在前面的话 我记得有一期罗胖的<罗辑思维>中他提到 我们在这个碎片化 充满焦虑的时代该怎么学习--用30%的时间 了解70%该领域的知识然后迅速转移芳草鲜美的地方 像游牧民族那样.原话应 ...

  5. Python测试开发-浅谈如何自动化生成测试脚本

    Python测试开发-浅谈如何自动化生成测试脚本 原创: fin  测试开发社区  前天 阅读文本大概需要 6.66 分钟. 一 .接口列表展示,并选择 在右边,点击选择要关联的接口,区分是否要登录, ...

  6. Unity教程之再谈Unity中的优化技术

    这是从 Unity教程之再谈Unity中的优化技术 这篇文章里提取出来的一部分,这篇文章让我学到了挺多可能我应该知道却还没知道的知识,写的挺好的 优化几何体   这一步主要是为了针对性能瓶颈中的”顶点 ...

  7. 浅谈线程池(中):独立线程池的作用及IO线程池

    原文地址:http://blog.zhaojie.me/2009/07/thread-pool-2-dedicate-pool-and-io-pool.html 在上一篇文章中,我们简单讨论了线程池的 ...

  8. 【ASP.NET MVC系列】浅谈NuGet在VS中的运用

    一     概述 在我们讲解NuGet前,我们先来看看一个例子. 1.例子: 假设现在开发一套系统,其中前端框架我们选择Bootstrap,由于选择Bootstrap作为前端框架,因此,在项目中,我们 ...

  9. 浅谈surging服务引擎中的rabbitmq组件和容器化部署

    1.前言 上个星期完成了surging 的0.9.0.1 更新工作,此版本通过nuget下载引擎组件,下载后,无需通过代码build集成,引擎会通过Sidecar模式自动扫描装配异构组件来构建服务引擎 ...

  10. 浅谈如何检查Linux中开放端口列表

    给大家分享一篇关于如何检查Linux中的开放端口列表的详细介绍,首先如果你想检查远程Linux系统上的端口是否打开请点击链接浏览.如果你想检查多个远程Linux系统上的端口是否打开请点击链接浏览.如果 ...

随机推荐

  1. CentOS7的网卡启动不起来的问题

    这个问题在刚学Linux遇到的,centOS7的网卡启动不起来,导致建不了集群.如下图没有ifconf-env33网卡的IP 还有下面这个图>>> 如果你遇到了,呵呵呵,迷之微笑. ...

  2. MySQL5.7使用过程中遇到的问题

    Q1.MySQL无法启动服务,启动服务时提示:"本地计算机 上的 MySQL 服务启动后停止.某些服务在未由其他服务或程序使用时将自动停止." PS.解压缩的MySQL安装过程也可 ...

  3. 微信小程序多宫格抽奖

    最近闲来无事,做了一个多宫格抽奖的例子,有什么需要改进或者错误的地方,请留言,谢谢 首先看效果 思路是先让其转动2圈多,然后再进行抽奖,格子运动用的是setTimeout,让其运行的时间间隔不一样,然 ...

  4. “玲珑杯”ACM比赛 Round #12 (D) 【矩阵快速幂的时间优化】

    //首先,感谢Q巨 题目链接 定义状态向量b[6] b[0]:三面临红色的蓝色三角形个数 b[1]:两面临红色且一面临空的蓝色三角形个数 b[2]:一面临红色且两面临空的蓝色三角形个数 b[3]:三面 ...

  5. [luogu P2184] 贪婪大陆 [树状数组][线段树]

    题目背景 面对蚂蚁们的疯狂进攻,小FF的Tower defence宣告失败……人类被蚂蚁们逼到了Greed Island上的一个海湾.现在,小FF的后方是一望无际的大海, 前方是变异了的超级蚂蚁. 小 ...

  6. 【.net 深呼吸】在运行阶段修改应用配置文件

    上一篇博文中,老周所介绍的自行编写的配置类,虽然能够很好地做封装,但它仅允许修改用户级别的配置,所以文件都是保存到用户配置目录下的.可是,许多情况下,我们还是不考虑用户隔离,而是能够直接修改与应用程序 ...

  7. 自带win10的笔记本电脑如何装win7

    网上那么多的装机教程,还有必要专门写一篇装机攻略么?有的,非常必要的!因为真的有很多未知的坑要趟!首先,win10好不好?除了正版,其他没什么好的...如果没有SSD,经常要卡死于磁盘读写.当然,你可 ...

  8. 机器学习(4)Hoeffding Inequality--界定概率边界

    问题 假设空间的样本复杂度(sample complexity):随着问题规模的增长导致所需训练样本的增长称为sample complexity. 实际情况中,最有可能限制学习器成功的因素是训练数据的 ...

  9. 强连通分量tarjan缩点——POJ2186 Popular Cows

    这里的Tarjan是基于DFS,用于求有向图的强联通分量. 运用了一个点dfn时间戳和low的关系巧妙地判断出一个强联通分量,从而实现一次DFS即可求出所有的强联通分量. §有向图中, u可达v不一定 ...

  10. [补档][NOI 2008]假面舞会

    [NOI 2008]假面舞会 题目 一年一度的假面舞会又开始了,栋栋也兴致勃勃的参加了今年的舞会.今年的面具都是主办方特别定制的.每个参加舞会的人都可以在入场时选择一个自己喜欢的面具. 每个面具都有一 ...