单元测试在Unity中的应用
单元测试在Unity中的应⽤
项⽬描述:简单演⽰单元测试在Unity中的应⽤
项⽬地址:
项⽬版本:2020.3.20f1
项⽬⽤法:打开就⽤,代码都放在 Assets/Editor内了
单元测试
简介
单元测试是指对软件中的最⼩可测试单元进⾏检查和验证,⼀般情况下就是对代码中的⼀个函数去进⾏验证,检查它的正确性
单元测试并不测基础结构问题(如数据库、⽂件系统和⽹络资源的交互等)
意义
节省开发期间的测试时间
相⽐于以往直接写业务代码、运⾏Unity跑功能、看断点看⽇志,单元测试能在编译器模式下快速执⾏业务逻辑的单元测试
有助于完善代码
因为能便捷的添加各类测试数据,所以编写测试代码期间就能发现正式业务代码需要注意的地⽅(如判空、合法性验证、边界问题、算法复杂度等)减少代码耦合
当代码紧密耦合时,可能难以进⾏单元测试。如果不为编写的代码创建单元测试,则耦合可能不太明显,为代码编写测试会⾃然地解耦代码
测试模式
采⽤ “Arrange、Act、Assert” 模式,主要包含3个操作:
安排对象,根据需要对其进⾏创建和设置
作⽤于对象
断⾔某些项按预期进⾏
Unity Test Runner
简介
Unity Test Runner 是 NUnit单元测试框架在Unity中的实现,可在编辑器模式下执⾏单元测试。
通过 Window->General->Test Runner 打开页⾯。双击某测试单元或左上⾓的 Run All、Run Selected ... 即可执⾏测试,并输出测试结果到控制台使⽤流程
rank函数的用法编写被测试代码
被测代码应当是剔除Unity组件交互、资源交互等后的核⼼算法逻辑。例如某功能模块下的某函数
若被测代码⾃⾝已较为独⽴(如各Utility类),则直接在测试代码内调⽤即可;否则应当新建被测试类进⾏测试
新建的被测试类⽂件可放在Asset->Editor下;采⽤测试功能名来命名即可
编写测试代码
测试代码需遵守“Arrange、Act、Assert”模式,且代码能简就简
测试函数需要打 [Test] 或 [TestCase] 标签,详见具体事例或NUnit Attribute
尽量减少if、switch、for等语句的使⽤(减⼩测试代码出bug的可能性)
Assert断⾔语句⼀旦测试失败即抛出,且失败⽇志的信息较少(只知道失败⾏和失败结果),因此可辅以Debug⽇志或断点调试
新建的测试类⽂件必须放在Asset->Editor下;采⽤测试功能名+Tests来命名
在Unity Test Runner 页⾯执⾏⽬标测试
选中较为常⽤的EditMode
选中各⾃需测试的单元执⾏测试即可(如某个测试类或该测试类下的某测试函数)
具体事例
事例1
需要测试GameUtils类下的获取字符串长度函数GetTextLength(),在各类传参下能否返回正确长度值。
先新建被测试类GameUtils及被测试函数GetTextLength():
public class GameUtils
{
public static int GetTextLength(string str)
{
// ---------- 错误:缺判空 ----------
// if (string.IsNullOrEmpty(str))
// {
//    return 0;
// }
int len = 0;
for (int i = 0; i < str.Length; i++)
{
byte[] byte_len = Encoding.UTF8.GetBytes(str.Substring(i, 1));
if (byte_len.Length > 1)
len += 2;
else
len += 1;
}
return len;
}
}
后新建GameUtils的测试⽤类GameUtilsTests,编写GetTextLength()的测试函数:
public class GameUtilsTests
{
// GetTextLength测试null字符串
[Test]
public void GetTextLength_NullStr()
{
string str = null;
int result = GameUtils.GetTextLength(str);
Assert.AreEqual(0, result);
}
/
/ 多测试数据的GetTextLength测试
[TestCase("", 0)]
[TestCase("Hello World", 11)]
public void GetTextLength_MultiTestData(string data, int exResult)
{
int result = GameUtils.GetTextLength(data);
Assert.AreEqual(exResult, result);
}
}
测试结果如下:
事例2
需要测试PVP排⾏榜的排序算法,是否能在单、多排序参数下正确得到排序数据。
先简化排⾏榜数据单元类为 PVPRankCell ,新建被测试类 PVPRankSort ,编写2个被测试函数,以及⽤于⽣成测试数据的函数 GenTestRankList() ://排⾏榜数据单元
public class PVPRankCell
{
public string Name;
public int Score;
public int RankInGlobal;
public long PlatformID;
}
public class PVPRankSort
{
public static int PVPRankCellComparer_BySingleComparedParam(PVPRankCell a, PVPRankCell b)
{
//return -a.PlatformID.CompareTo(b.PlatformID); //错误
return a.PlatformID.CompareTo(b.PlatformID);    //正确
}
public int PVPRankCellComparer_ByMultiComparedParam(PVPRankCell a, PVPRankCell b)
{
if (a.Score != b.Score)
return -a.Score.CompareTo(b.Score);
if (a.RankInGlobal != b.RankInGlobal)
return a.RankInGlobal.CompareTo(b.RankInGlobal);
return -a.PlatformID.CompareTo(b.PlatformID); //错误
//return a.PlatformID.CompareTo(b.PlatformID);    //正确
}
// ⽣成测试⽤数据
public List<PVPRankCell> GenTestRankList()
{
List<PVPRankCell> testRankList = new List<PVPRankCell>
{
new PVPRankCell() {Name = "A", Score = 10, RankInGlobal = 3, PlatformID = 1001},
new PVPRankCell() {Name = "B", Score = 10, RankInGlobal = 3, PlatformID = 1002},
new PVPRankCell() {Name = "C", Score = 10, RankInGlobal = 3, PlatformID = 1002},    //隐患数据
new PVPRankCell() {Name = "D", Score = 20, RankInGlobal = 1, PlatformID = 1003},
new PVPRankCell() {Name = "E", Score = 30, RankInGlobal = 2, PlatformID = 1004},
};
return testRankList;
}
}
后新建测试类 PVPRankSortTests,编写2个排序算法的测试函数:
public class PVPRankSortTests
{
PVPRankSort PvpRankSort;
[SetUp]
public void SetUp()
{
//最先执⾏的⽅法,作为多测试⽅法的功能部分
PvpRankSort = new PVPRankSort();
}
[TearDown]
public void TearDowm()
{
//最后执⾏的⽅法,⽤于清除或回收公共资源
PvpRankSort = null;
}
/
/ 单⼀⽐较参数排序算法的测试
[Test]
public void PVPRankSort_SingleComparedParam()
{
// Arrange:安排对象,根据需要对其进⾏创建和设置
//        如构造测试⽤数据
List<PVPRankCell> testRankList = PvpRankSort.GenTestRankList();
// Act:作⽤于对象
//        如具体算法实现
testRankList.Sort(PVPRankSort.PVPRankCellComparer_BySingleComparedParam);
// Assert:断⾔某些项按预期进⾏
/
/        如结果校验:PlatformID升序
for (int index = 0; index + 1 < testRankList.Count; ++index)
{
if (testRankList[index].PlatformID != testRankList[index + 1].PlatformID)
Assert.Less(testRankList[index].PlatformID, testRankList[index + 1].PlatformID); //PlatformID升序
else
Debug.LogWarning($"Warning>>>>>  {testRankList[index].Name} 的排序参数和 {testRankList[index + 1].Name} ⼀致"); //隐患情况
}
}
// 多⽐较参数排序算法的测试
[Test]
public void PVPRankSort_MultiComparedParam()
{
// Arrange:安排对象,根据需要对其进⾏创建和设置
//        如构造测试⽤数据
List<PVPRankCell> testRankList = PvpRankSort.GenTestRankList();
// Act:作⽤于对象
//        如具体算法实现
testRankList.Sort(PvpRankSort.PVPRankCellComparer_ByMultiComparedParam);
// Assert:断⾔某些项按预期进⾏
//        如结果校验:分数降序->名次升序->PlatformID升序
for (int index = 0; index + 1 < testRankList.Count; ++index)
{
if (testRankList[index].Score != testRankList[index + 1].Score)
Assert.Greater(testRankList[index].Score, testRankList[index + 1].Score); //分数降序
else if (testRankList[index].RankInGlobal != testRankList[index + 1].RankInGlobal)
Assert.Less(testRankList[index].RankInGlobal, testRankList[index + 1].RankInGlobal); //排名升序
else if (testRankList[index].PlatformID != testRankList[index + 1].PlatformID)
Assert.Less(testRankList[index].PlatformID, testRankList[index + 1].PlatformID); //PlatformID升序
else
Debug.LogWarning($"Warning>>>>>  {testRankList[index].Name} 的排序参数和 {testRankList[index + 1].Name} ⼀致"); //隐患情况
}
}
}
测试结果如图:
其他
NUnit Attribute
TestAttribute
常⽤标签,标记该⽅法能被执⾏测试,⽅法必须为public void ⽆参
// GetTextLength测试null字符串
[Test]
public void GetTextLength_NullStr()
{
string str = null;
int result = GameUtils.GetTextLength(str);
Assert.AreEqual(0, result);
}
TestCaseAttribute
标记该⽅法能被执⾏测试,⽅法必须为public void,可传参,参数由TestCase传⼊
// 多测试数据的GetTextLength测试
[TestCase("", 0)]
[TestCase("Hello World", 11)]
public void GetTextLength_MultiTestData(string data, int exResult)
{
int result = GameUtils.GetTextLength(data);
Assert.AreEqual(exResult, result);
}
TestFixtureAttribute
暂⽆需使⽤。⽤于标记⼀个类为测试类,其中此类必须是public,必须保证此构造函数不能有任何的副作⽤(不能出现异常或者错误的情况),在⼀个测试过程中,可以被构造多次。如果构造函数带有参数,可以指定默认的初始化参数
SetUpAttribute
标记该⽅法在测试流程中被⾸先执⾏,⽤作初始化公共参数
PVPRankSort PvpRankSort;
[SetUp]
public void SetUp()
{
//最先执⾏的⽅法,作为多测试⽅法的功能部分
PvpRankSort = new PVPRankSort();
}
TearDownAttribute
标记该⽅法被最后执⾏,⽤作回收公共参数部分,与SetUp配对使⽤[TearDown]
public void TearDowm()
{
//最后执⾏的⽅法,⽤于清除或回收公共资源
PvpRankSort = null;
}
CategoryAttribute
给该测试⽅法打筛分标签,在UnityTestRunner页⾯可筛分显⽰(但有特殊字符限制)RepeatAttribute
标记该测试⽅法重复执⾏指定次数
参考⽂章

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。