1. 为什么我会把数值测试单独拎出来讲
1.1 一次线上事故让我开始认真对待数值测试
事情发生在某个手游版本的平衡性调整里。策划把某个技能的基础伤害从120改成了135,数值表在配置后台轻轻一点,客户端同步后上架。结果第二天玩家社区就炸了,有人贴出伤害截图,说实际伤害比配置高了8%,怀疑暗改。排查到最后,问题不是策划配错,而是技能伤害公式里的一个乘算节点用了整数除法,baseDamage * coefficient / 100 在系数小于100时被截断成了0,导致后续加成全乱。更尴尬的是,当时项目里没有一条测试能拦住这种问题。
从那天起我开始意识到,Unity项目里的"数值测试",和一般意义上的"单元测试"不完全是一回事。单元测试更多是在验证"这段逻辑跑对了没有",而数值测试要回答的问题是"这组数值在各种输入、随机、边界、配置组合下,是否符合预期"。后者在很大程度上是数据驱动和统计驱动的,恰恰是游戏项目里最容易出事、又最容易被忽视的部分。Unity TestFramework(以下简称UTF)提供的基础设施,刚好能把这些验证固化下来。
1.2 数值测试和逻辑单元测试的差异
很多Unity开发者对单元测试的印象还停留在"写几个断言,保证函数不报错"。这类测试当然有用,但用它来测数值系统时,常见的问题是:测试写得太浅,根本没覆盖到策划真正关心的东西。
我举几个具体差异:
- 普通单元测试关注"真/假",数值测试关注"数值是否落在预期区间",往往还要考虑精度。
- 普通单元测试直接调用某个方法,数值测试经常需要把一个带有随机性的过程跑很多次,用统计结果来判断逻辑是否合理。
- 普通单元测试的输入是手工指定的边界值,数值测试的输入有很大一部分来自配置文件、数据表,需要把"配置-代码-实际运行结果"串成同一条验证链。
- 普通单元测试失败时定位到具体函数,数值测试失败时往往要区分是公式写错、配置配错、还是随机性导致的偶发差异。
UTF本身是基于NUnit的,支持[Test]、[UnityTest]、Assert系列方法、TestCase参数化等能力。做数值测试时,我一般会把它当做一个轻量级的"数值验证平台"来用,而不是只当作单元测试工具。
1.3 这套方案适用谁、解决什么
如果你是单机小项目里的独立开发者,数值公式写在Update里,改一个数字全靠肉眼观察伤害跳字,那么这套方案可能显得有点重。但只要你的项目里存在以下任何一种情况,UTF做数值测试就很有必要:
- 数值公式在多个副本、多个技能、多个角色之间共用;
- 配置表由策划或后端维护,客户端代码和配置经常异步更新;
- 随机掉落、暴击、命中这类概率逻辑需要保证线上表现和配置一致;
- 已经出现过"公式改了A功能,B功能跟着出错"的情况;
- 团队里有多个人在动数值相关代码。
这套方案能解决的核心问题,就是让数值的验证前置到提交之前,而不是让玩家帮你测出来。接下来我把搭建环境和写测试的完整流程讲一遍。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 搭建Unity TestFramework的环境与第一个断言
2.1 环境准备:通过Package Manager安装Test Framework
Unity TestFramework在2019.2之后的版本里都作为官方包提供,推荐用Package Manager安装。打开菜单的Window > Package Manager,在左上角包类型里选择Unity Registry,搜索Test Framework,点击Install就好。Unity 2020之后一般会默认带com.unity.test-framework,如果是新项目,Window > General > Test Runner面板里也会提示你安装。
装完之后,项目里会出现两个程序集定义文件的位置差异,这是新手最容易困惑的地方。UTF把测试分成两种模式:
- EditMode Tests:运行在编辑器环境里,不进入Play模式,适合测试纯逻辑、纯数值、配置解析这类和引擎运行时无关的代码。
- PlayMode Tests:会真正进入Play模式,适合测试MonoBehaviour生命周期、协程、物理、场景加载等依赖运行时状态的逻辑。
数值测试绝大多数属于纯逻辑验证,放到EditMode里跑就够,速度极快,也适合做提交检查。如果你的数值计算依赖了Time.deltaTime、Random的引擎实例、或者Unity的Mathf在特定平台上的实现,再考虑PlayMode。我自己有一个原则:能进EditMode就不去写PlayMode,因为后者跑一轮要启动Play模式,哪怕场景很空,每次也要多花好几秒。
2.2 在编辑器和Console跑通第一个EditMode测试
安装完成后,打开Window > General > Test Runner,选择EditMode页签,点击Create EditMode Test Assembly Folder,Unity会自动创建一个Tests目录,里面包含一个TestAssembly的asmdef文件和一个示例测试脚本。
一个最朴素的数值测试可以这样写:
csharp复制using NUnit.Framework;
public class NumbericTestExample
{
[Test]
public void 加法_符合交换律()
{
int a = 5;
int b = 7;
int result = a + b;
Assert.AreEqual(12, result);
}
}
这个测试在Test Runner里会以方法名的形式显示,中文方法名在UTF里也可以正常显示。实际项目里我见过不少团队纠结测试方法命名规范,我的建议是直接使用"行为描述"风格,例如暴击伤害_在破甲状态下_不低于基础值。虽然有点啰嗦,但失败时看测试列表就能明白是哪个数值场景出了问题。
点击Run All,如果一切正常,测试会显示绿色通过。这是最基础的闭环。但数值测试真正难的不是跑通一个断言,而是想清楚"到底要断言什么"。
2.3 关于浮点数比较:第一个坑
数值系统里大量使用浮点数,而浮
