知识点涉及
文章主要涉及以下知识点:
- 单元测试基本概念(必须)
- Unity的内置测试工具Unity Test Framework(UTF)(必须)
- 依赖注入概念(必须)
- 面向数据设计,本文中使用的是UniRx框架(非必要)
单元测试的必要性
- 验证代码设计的正确性
- 重构代码的安全网
- 排除潜在的危险
- 加深对代码理解
- 代码审查的资料
- 增加代码的茁壮性 我认为在Unity中,需要进行测试的点主要包括:
测试点选择
- 公共方法
- 公共属性
- Hot Observable and cold Observable
- 积极测试和消极测试
选择这些点的原因是:
- 公共方法和属性访问性高;对公共方法的测试也隐式的对私有方法进行了测试
- Observable相当C#中的Event,是游戏中各个组件、系统之间连接的纽带,必须测试
- 积极的测试是希望得到一个True的结果,期望程序按照预期运行;消极的测试则是希望得到一个False结果,预见出错的条件,并设置适当的出错处理。
适合使用EditorMode进行测试的测试点
当测试点满足以下条件时,适合采用EditorMode进行测试:
- 测试点不依赖于第三方脚本
- 测试点具备量化指标。如果测试的点不具备量化指标,可能是因为方法太复杂了,可以尝试将大的方法拆分成单一的、较小的方法。
使用依赖注入破除依赖
如何编写在Unity中优秀的单元测试需要我们积累大量的经验和深入的思考,这里介绍一种比较棘手的情况: 对于脚本A方法的测试必须要依赖于脚本B,我们应该如何对脚本A进行测试。
我采取的策略是:将脚本A和脚本B的公共方法封装到脚本C的接口中,对脚本C的接口进行一系列的测试从而保证脚本A和脚本B的正确性。接下来我将以一个InventorySystem(背包系统)的设计进行阐述。
背包系统设计简述: InventorySystem由InventoryPresenter和InventoryGui组成。其中InventoryPresenter处理数据层的逻辑业务,InventoryGui则是将数据的变化映射到Scene中。IInventorySystem的具体实现交由 InventoryPresenter和InventoryGui,InventorySystem只是个适配器将这两个模块组合在一起。InventorySystem的代码见List1。 我的测试目的是想验证presenter和gui的方法是否按照我预想的进行运作。 List.1
public class InventorySystem : MonoBehaviour,IInventorySystem
{
InventoryPresenter presenter;
InventoryGui gui;
//implement interface
...
public InventorySetting setting;
public void Init()
{
presenter = new InventoryPresenter(this);
gui = FindObjectOfType<InventoryGui>();
Assert.IsNotNull(gui, "Failed to find InventoryGui script in Hierarchy");
gui.InitModule(this);
}
private void OnEnable()
{
Init();
}
}
在List.1中,InventorySystem主要由一下部分组成:
- presenter处理数据逻辑,gui处理视图层逻辑。
- 接口实现:接口方法和属性都只是presenter和gui方法和属性的包装。
- Init方法:采用依赖注入方式实例化一个presenter,采用FindObject的方式激活gui,详细见InventorySystemWorkshop.scene。Init方法可以在单元测试中手动运行,自动产生presenter和gui方便之后的测试。
- 在OnEnable中运行Init方法,完成系统初始化。
EditMode单元测试
在介绍测试脚本之前,我想先介绍下EditMode的运行特点:
- 测试方法只运行一次 ,没有callback
- 运行期间不会清空Hierarchy中的Game 这两个特性让我可以有效测试InventorySystem脚本在游戏场景中的表现。
InventorySystemTest介绍
在对InventorySystem进行测试时,只需对其接口进行测试,这样做有以下好处:
- 公共方法测试起来访问性高,不需要诸如抽取重构等手段。
- 公共方法肯定是调用过私有方法的,对公共方法的测试其实也隐式包含了对私有方法的测试。
- 采用依赖注入设计的InventorySystem脚本是非常适合在EditMode下进行单元测试。
- EditMode运行速度是远超过PlayMode,加快了测试进度。
下面结合部分InventorySystemTest代码进行要点说明,见List2。
public class InventorySystemTest
{
//初始化化InventorySystem
private InventorySystem Config()
{
var inventory = GameObject.FindObjectOfType<InventorySystem>();
inventory.Init();
return inventory;
}
[Test]
public void AddItem_true_5()
{
var inventory = Config();
inventory.AddItem("iron", 5);
var hasIron = inventory.HasItem("iron");
var amount = inventory.GetAmount("iron");
Assert.IsTrue(hasIron);
Assert.AreEqual(5, amount);
}
[Test]
public void OnInventoryChanged_AddItem_receive5()
{
var inventory = Config();
inventory.OnInventoryChanged
.Subscribe(x =>
{
Assert.AreEqual(5, x.NewValue.Amount);
Assert.AreEqual("copper", x.NewValue.Name);
});
inventory.AddItem("copper", 5);
}
}
Config方法的职责是:获取Hierarchy中的InventorySystem的引用,并对其执行初始化操作(EditMode的特性2允许我这样操作)。 AddItem_true_5方法的测试目标:是向背包中添加5个iron,并查询,期望得到一个返回值5。 OnInventoryChanged_AddItem_receive5方法的测试目标是:是向背包中添加5个copper,欲检验OnInventoryChanged在AddItem事件发生时,是否推送了背包数据。预期是得到int 5和string “copper”。 经过测试,这两个方法通过了单元测试。所以我可以认为:这两个方法的实现是无误的。
本文介绍了在Unity中进行单元测试的重要性,特别是针对公共方法和属性、Observable以及积极和消极测试的选择。文章通过一个InventorySystem的案例,展示了如何利用Unity的内置测试工具UTF进行测试,以及如何通过依赖注入来解除脚本间的耦合。作者还讨论了EditMode测试的适用场景,并提供了一个InventorySystemTest的示例,验证了InventorySystem的正确性。
701

被折叠的 条评论
为什么被折叠?



