由于测试需要加载游戏,所以我们需要等待一些时间才能开始游戏测试,通常是需要数十秒或者更久。之所以说测试结果不可靠,是因为同时加载了太多与我们测试目的不相关的代码,但有很多bug会导致我们的测试失败,因此,你很难确切地知道某次测试为什么失败,实际上,我们的测试方法是有问题的。
这些测试写起来也会很难,由于我们需要用间接的方式搞定测试。比如,如果我们想要在游戏世界里测试(武器的)格挡功能,就需要把它放到玩家背包、让玩家装备它,盯着特定方向,然后使用格挡。假如我们只是想要测试格挡,就不必要地加载了背包和玩家相关的很多代码,这就导致在测试格挡功能的时候有很多因素会导致失败。
作为程序员,我们很幸运地可以对单个物品进行测试,我们必须要像人工测试那样通关整个游戏,而是可以调用一个函数进行单元测试。单元测试主要是对特定一批代码进行测试,这批代码是与游戏的其他部分隔离开的,这是测试策略很基础的部分。
单元测试速度很快,因为不需要处理大量不相关的代码,所以每次测试可能只需要几毫秒;它们也非常可靠,因为它只会执行我们真正在测试的代码。这种方法也很容易写,不仅是因为设置起来更容易,还因为我们可以直接调用需要测试的代码,所以我们非常熟悉API,因为我们已经写好了。
然而,当写代码的时候就考虑测试,那么做单元测试就会有些困难,因为这通常需要重构引擎级的代码。即使我们写了引擎代码、也愿意进行修改,如果没有开始的测试,就可能会带来bug,所以我们经常会发现重构代码来进行单元测试太过于冒险。所有这些都让单元测试几乎是不可能的,至少是大部分游戏代码都不太可行。
所以,《我的世界》当中测试也是一样,即使我们加入了功能测试框架和单元测试框架,我们想要将它写到任何测试当中使用也是有困难的。这里举一个单元测试有关的架构问题:
假设我想要测试一个组件的方法,要创造组件就需要有Actor,这就需要有一个关卡,而想要做一个关卡,就需要主游戏类,而这太大而且太复杂,只有通过运行游戏才能得到,因此我无法对组件进行单元测试。
2、满足代码库要求
当功能或者集成测试不理想的时候,就不可能进行单元测试,所以,我们现在就需要一些满足代码库要求的框架,并且为游戏代码解锁单元测试。
我们在《盗贼之海》和《我的世界》使用都非常成功的一个技巧,就是在单元测试和功能测试之间的“中间”测试,随后我会介绍它在两个项目中看起来是怎样的。
《盗贼之海》使用的方法我们称之为Actor测试,它将引擎的可解锁或可修改部分作为首要类依赖关系。因此,如果你需要一个关卡复活一个Actor,就需要一个真正的关卡才能做到,而不是尝试模仿这个关卡。
所以,不要破坏依赖关系让它独立,相反,你需要满足依赖关系,然后写那些看起来像是单元测试的代码,这些写起来简单、可靠而且容易。
这里Robert局的例子当中,他测试的是白天的影之骷髅,正确地将其从黑暗切换到了光状态,我们可以看到,他只是用了几行同步代码就实现了这一点。他首先创造了一个影之骷髅,并且确保它是在黑暗状态,然后将游戏世界改成中午,然后通过调用光将影之骷髅设定为光状态。
通过这几个简单的调用代码,他有效地对自己的代码进行了单元测试,即使他的代码并不是与引擎分离的,所以这本质上并不是真正的单元测试。这些类型的测试很容易写,运行很快而且可靠,也是单元测试和功能测试很好的平衡,因此它们占据《盗贼之海》70%的测试也就不令人意外了。
对于《我的世界》,我们通过《盗贼之海》的Actor测试借鉴灵感,创造了一种我们成为服务器测试的方法。在服务器测试中,我们通过加载平坦的游戏世界来满足引擎依赖关系,并且让测试可以触及到它。
之所以称之为服务器测试,是因为我们现在很多的测试都是运行在客户端,但有趣的事情却发生在服务器侧,但令人意外的是,它与Actor测试很像。
这个案例中,我们想让蜂巢在被标记之后不再生出蜜蜂,如Robert的案例那样,只需要几行代码就能完成。首先我们生成一个蜂巢Actor,然后直接加上标记方法,然后,我们通过直接调用Actor,让它不想再生出蜜蜂。
这种方法不受太多因素影响,而且直接调用方法,因此它不需要等待时机,也不需要主要的因素和引擎分开,因为我们提供了一个真正的关卡并复活了真实的Actor,才运行了单元测试。如果是用功能测试,就需要做更多的工作,因为我们需要找到蜜蜂、计算世界里的蜜蜂数量,然后再次计算,确保我们的蜜蜂数量不变,这不仅难写,也会需要很长时间运行,还可能有更多地方出错。
所以,就像Actor测试那样,服务器测试非常方便,我们目前在《我的世界》用的超过4000个,占据了这款游戏测试总量的35%。
如果今天一定要强调某件事,我觉得单元测试和功能测试之间的“中间测试”就是最重要的,它可以对旧代码进行测试,而且也应该可以用于任何一款游戏。目前为止,我们发现这个方法在虚幻引擎上可以使用,也能在《我的世界》引擎上运行。
3、偏重采用
对于采用,我指的是持续、广泛的测试授权,所以开发者在为他们大多数的改变写代码。为少数人做少量测试写框架相当容易,但是,想要达到让每个人大部分时间都使用它,是非常有挑战性的。而且,我觉得这是任何测试框架当中,最重要也最优先的问题。
比如,在之前一个测试当中,我写了一个自己觉得很好而且很完美的框架,甚至还简单易懂。悲剧的是,没有人使用这个框架,因为在这里面写测试有太多的分歧,你需要打开一个单独的项目用两种语言写代码,然后还需要写游戏代码将功能暴露给框架,测试结果也不那么可靠。所以,我在《盗贼之海》汲取了教训,写了一个真正让人们使用的框架。
在《我的世界》项目上,我们的框架已经内置在游戏里,因此不存在那样的分歧,但依然因为其他原因存在一些分歧。因此,我们第一步就是承认这不适合我们,即便游戏里已经有一个投入努力就可以用的框架,可是并没有人真正用它来写测试。
所以,我们聚焦于写一个真正实用的框架,甚至要考虑技术债,满足开发者们的要求,让它写一个测试比不写测试更容易用,因为我们认为,帮助人们走出第一步是写测试很重要的一方面。做到这些远不只是易于测试的框架,还需要做出文化上的改变,有对的框架是很必要的。