3

《我的世界》首席软件架构师分享:自动化测试的6个经验心得

 2 years ago
source link: http://www.gamelook.com.cn/2022/09/497805
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

《我的世界》首席软件架构师分享:自动化测试的6个经验心得

2022-09-20 • 游戏测试

【GameLook专稿,未经授权不得转载!】

GameLook报道/如今大部分游戏都是网络游戏,或者更确切的说,是服务型游戏(GaaS)。这些游戏的更新频率更高,仅靠人工测试效率越来越低下,越来越耗时,而且成本越来越高。很多工作室都在尝试自动化测试,但往往发现创造快速、可靠并且真正有价值的自动化测试,是一场看不到结束的艰难战斗。

在此前的GDC大会上,微软旗下Mojang工作室首席软件架构师Henry Golding分享了《我的世界》自动化测试的经验和技巧,他还曾是《盗贼之海》自动化测试负责人。

以下是Gamelook听译的完整内容:

Henry Golding:

lazy.png

我是来自Mojang工作室的Henry Golding,我们将要分享的是,当尝试用《盗贼之海》同样的方法测试《我的世界》时,我们学到了什么。我会尝试总结一些方法和技巧,希望能够对其他项目的自动测试也能带来帮助。

我从事游戏行业已经12年了,过往的经验基本上平均分布于玩法、程序和自动测试三个领域。我参与过多个项目的研发,和今天相关的是曾经在《盗贼之海》项目带领自动测试框架团队,目前在《我的世界》团队担任同样的职位。

为何要做自动化测试?

在我从业期间,我们发布游戏的方式发生了很大的变化:刚开始的时候,我们把游戏写到一张磁盘上就结束了,后续还可以发布补丁、DLC。整个游戏行业趋势转向了GaaS,游戏发布更新越来越频繁,《盗贼之海》与《我的世界》都是很好的案例。

lazy.png

行业趋势向服务转变带来了很大的问题,自动测试变得至关重要,游戏已经大到不可能人工测试了。我们过去曾经只依赖于人工测试,当你只想做一两次测试的时候,代价可能是有些昂贵的,但当你经常发布更新的时候,就需要不断地支付这个费用,测试成本就会大到难以承受。

对于《盗贼之海》和《我的世界》这样的即兴玩法沙盒游戏来说,很难在有信心发布的同时做到比较高的测试性价比,所以很多工作室都在寻找另一个方式。

lazy.png

在《盗贼之海》这个项目上,我们解决这个问题的方法就是采用持续交付,在2018年的GDC大会上,Jafar Soltani讲述了这个方法,以及我们通过它得到的帮助。基本上来说,持续交付就是能够在任何你想要的时候发布内容,因为你总能将代码保持在稳定状态。当然,这件事说起来容易做起来难,但你可以想象,自动测试是持续交付的基础,因为它可以让你的代码维持在稳定状态。

自动化测试是《盗贼之海》项目研发很重要的一部分,我们知道,为了实现持续交付,我们需要以快速可靠的方式验证游戏,所以我们打造每个功能都要证明它们是行之有效的,2019年的时候,Robert Masella专门就这个问题做了一次演讲。可能有人记得这次演讲,因为在GDC历史上,首次有人把玩法用函数形式写出来。

lazy.png

自动化测试很重要的一个功能,就是谁来写测试,那就是让游戏开发者来写测试代码,包括玩法代码,我觉得这是最有效率的方法。在软件行业,这基本上也是所有领域的标准做法,而且开发者也可以对给玩家带来高品质体验投入热情,当有了对的工具和训练,我们实际上可以将它做的非常好。

lazy.png

《盗贼之海》与《我的世界》是有些共同点的,但也有些关键的差异。对于《盗贼之海》,我们可以完全从头开始,游戏是从一个小团队开始研发的,所以我们可以从一开始写代码的时候就保证它能用来测试。《我的世界》是一款十多年的老游戏,它的代码并不是为了测试而设计的,然而我们始终都有数百名开发者对其做出改变。

lazy.png

然而它们也有些相似性,由于开放世界属性,两款游戏都很难讲明白,而且两者都频繁发布更新内容,所以都很适合持续内容交付。刚开始的时候,两个团队都不熟悉自动化测试,所以我们可以开始就在两个团队都形成一个文化。

目标:让开发者写测试

lazy.png

假设我们都对持续内容交付很感兴趣,并且希望我们的开发者们写出快速、可靠的自动化测试成为他们日常工作的一部分,在他们写代码的时候,就能快速找到bug所在。那么,我们该怎么做到呢?

这是很有挑战性的,我见过这样的尝试失败,也见过成功的,不过,我们在《盗贼之海》和《我的世界》都做成了,接下来我就说一些在两个项目上都有用的主题和方法。

《我的世界》自动化测试技巧

lazy.png

简短来说,它们分别是:让一个小团队负责过渡、满足代码库的需求、偏重采用,然后规模化、让开发者作为合作伙伴一样参与其中,以及从领导层得到对应的支持。

1、安排团队负责过渡

lazy.png

采取自动化测试通常试一次新奇未知的冒险,当我们不适应这种方式的时候,需要克服许多的障碍,需要有人来带来这种文化上的改变。需要有专门的团队写专门的工具或框架,教开发者们使用,以及持续提供的支持并打造社区,任何一种情况下,两三人的团队就已经够用了。

为了给下一个方法打下基础,我们来探索写测试的一些不同方式。我注意到,作为开发者,当我们开始思考自动化测试的时候(包括我也不例外),都倾向于以玩家角度进行测试。这是非常自然的现象,因为传统意义上,我们就是通过自己亲身体验来测试游戏的,因此在设计自动化测试的时候,我们很容易参考人工测试的方法。

lazy.png

对于这些测试,我们给出了很多种名字,功能测试、集成测试、端到端测试,这是很常见的测试起点,《我的世界》最初也是这么测试的。尽管这样的测试有成熟的框架,但通常情况下,这些测试往往很慢,而且不可靠、很难规模化。

由于测试需要加载游戏,所以我们需要等待一些时间才能开始游戏测试,通常是需要数十秒或者更久。之所以说测试结果不可靠,是因为同时加载了太多与我们测试目的不相关的代码,但有很多bug会导致我们的测试失败,因此,你很难确切地知道某次测试为什么失败,实际上,我们的测试方法是有问题的。

这些测试写起来也会很难,由于我们需要用间接的方式搞定测试。比如,如果我们想要在游戏世界里测试(武器的)格挡功能,就需要把它放到玩家背包、让玩家装备它,盯着特定方向,然后使用格挡。假如我们只是想要测试格挡,就不必要地加载了背包和玩家相关的很多代码,这就导致在测试格挡功能的时候有很多因素会导致失败。

lazy.png

作为程序员,我们很幸运地可以对单个物品进行测试,我们必须要像人工测试那样通关整个游戏,而是可以调用一个函数进行单元测试。单元测试主要是对特定一批代码进行测试,这批代码是与游戏的其他部分隔离开的,这是测试策略很基础的部分。

单元测试速度很快,因为不需要处理大量不相关的代码,所以每次测试可能只需要几毫秒;它们也非常可靠,因为它只会执行我们真正在测试的代码。这种方法也很容易写,不仅是因为设置起来更容易,还因为我们可以直接调用需要测试的代码,所以我们非常熟悉API,因为我们已经写好了。

然而,当写代码的时候就考虑测试,那么做单元测试就会有些困难,因为这通常需要重构引擎级的代码。即使我们写了引擎代码、也愿意进行修改,如果没有开始的测试,就可能会带来bug,所以我们经常会发现重构代码来进行单元测试太过于冒险。所有这些都让单元测试几乎是不可能的,至少是大部分游戏代码都不太可行。

lazy.png

所以,《我的世界》当中测试也是一样,即使我们加入了功能测试框架和单元测试框架,我们想要将它写到任何测试当中使用也是有困难的。这里举一个单元测试有关的架构问题:

假设我想要测试一个组件的方法,要创造组件就需要有Actor,这就需要有一个关卡,而想要做一个关卡,就需要主游戏类,而这太大而且太复杂,只有通过运行游戏才能得到,因此我无法对组件进行单元测试。

2、满足代码库要求

lazy.png

当功能或者集成测试不理想的时候,就不可能进行单元测试,所以,我们现在就需要一些满足代码库要求的框架,并且为游戏代码解锁单元测试。

我们在《盗贼之海》和《我的世界》使用都非常成功的一个技巧,就是在单元测试和功能测试之间的“中间”测试,随后我会介绍它在两个项目中看起来是怎样的。

lazy.png

《盗贼之海》使用的方法我们称之为Actor测试,它将引擎的可解锁或可修改部分作为首要类依赖关系。因此,如果你需要一个关卡复活一个Actor,就需要一个真正的关卡才能做到,而不是尝试模仿这个关卡。

所以,不要破坏依赖关系让它独立,相反,你需要满足依赖关系,然后写那些看起来像是单元测试的代码,这些写起来简单、可靠而且容易。

这里Robert局的例子当中,他测试的是白天的影之骷髅,正确地将其从黑暗切换到了光状态,我们可以看到,他只是用了几行同步代码就实现了这一点。他首先创造了一个影之骷髅,并且确保它是在黑暗状态,然后将游戏世界改成中午,然后通过调用光将影之骷髅设定为光状态。

通过这几个简单的调用代码,他有效地对自己的代码进行了单元测试,即使他的代码并不是与引擎分离的,所以这本质上并不是真正的单元测试。这些类型的测试很容易写,运行很快而且可靠,也是单元测试和功能测试很好的平衡,因此它们占据《盗贼之海》70%的测试也就不令人意外了。

lazy.png

对于《我的世界》,我们通过《盗贼之海》的Actor测试借鉴灵感,创造了一种我们成为服务器测试的方法。在服务器测试中,我们通过加载平坦的游戏世界来满足引擎依赖关系,并且让测试可以触及到它。

之所以称之为服务器测试,是因为我们现在很多的测试都是运行在客户端,但有趣的事情却发生在服务器侧,但令人意外的是,它与Actor测试很像。

这个案例中,我们想让蜂巢在被标记之后不再生出蜜蜂,如Robert的案例那样,只需要几行代码就能完成。首先我们生成一个蜂巢Actor,然后直接加上标记方法,然后,我们通过直接调用Actor,让它不想再生出蜜蜂。

这种方法不受太多因素影响,而且直接调用方法,因此它不需要等待时机,也不需要主要的因素和引擎分开,因为我们提供了一个真正的关卡并复活了真实的Actor,才运行了单元测试。如果是用功能测试,就需要做更多的工作,因为我们需要找到蜜蜂、计算世界里的蜜蜂数量,然后再次计算,确保我们的蜜蜂数量不变,这不仅难写,也会需要很长时间运行,还可能有更多地方出错。

所以,就像Actor测试那样,服务器测试非常方便,我们目前在《我的世界》用的超过4000个,占据了这款游戏测试总量的35%。

如果今天一定要强调某件事,我觉得单元测试和功能测试之间的“中间测试”就是最重要的,它可以对旧代码进行测试,而且也应该可以用于任何一款游戏。目前为止,我们发现这个方法在虚幻引擎上可以使用,也能在《我的世界》引擎上运行。

3、偏重采用

lazy.png

对于采用,我指的是持续、广泛的测试授权,所以开发者在为他们大多数的改变写代码。为少数人做少量测试写框架相当容易,但是,想要达到让每个人大部分时间都使用它,是非常有挑战性的。而且,我觉得这是任何测试框架当中,最重要也最优先的问题。

比如,在之前一个测试当中,我写了一个自己觉得很好而且很完美的框架,甚至还简单易懂。悲剧的是,没有人使用这个框架,因为在这里面写测试有太多的分歧,你需要打开一个单独的项目用两种语言写代码,然后还需要写游戏代码将功能暴露给框架,测试结果也不那么可靠。所以,我在《盗贼之海》汲取了教训,写了一个真正让人们使用的框架。

在《我的世界》项目上,我们的框架已经内置在游戏里,因此不存在那样的分歧,但依然因为其他原因存在一些分歧。因此,我们第一步就是承认这不适合我们,即便游戏里已经有一个投入努力就可以用的框架,可是并没有人真正用它来写测试。

所以,我们聚焦于写一个真正实用的框架,甚至要考虑技术债,满足开发者们的要求,让它写一个测试比不写测试更容易用,因为我们认为,帮助人们走出第一步是写测试很重要的一方面。做到这些远不只是易于测试的框架,还需要做出文化上的改变,有对的框架是很必要的。

4、规模化

lazy.png

采用之后就是规模化,我们发现遇到了新的、更好的问题,比如有很多测试耗费的时间比我们想象的更长。为了节约测试时间,《盗贼之海》支持世界旅行,以减少测试初始化占用的时间,对于《我的世界》我们将同一个关卡的测试分组,以尽可能避免重复加载一个关卡。

这给我们减少了90%的时间,但也是有代价的,比如不同测试之间的状态泄露,如果解决不当,就有可能导致测试失败,因为这个关卡之前已经运行过了,而且这种失败很难追踪。

大量的测试会导致很难搞清楚其中一个失败的原因,假设当你复活一个Actor的时候有1%的概率导致游戏崩溃,任何需要复活这个Actor的测试都可能受到影响,考虑到我们的测试数量之多,遇到这个崩溃bug的概率就会高很多。为此,我们在《我的世界》测试中增加了自动重试功能,所以在被认定为真正失败之前,这个测试必须自动重试,这帮助开发者减少了噪音。

两款游戏都使用了隔离方法,主要是为了确保测试结果可靠。当然,这些问题只影响服务器或者功能测试,所以我们鼓励优先采取单元测试,以避免这些“成长的烦恼”。

5、让开发者们参与

接下来我想聊聊开发者们在这种转型当中的角色,虽然两个案例中我们都只投入了很小的团队进行过渡,但真正成功的是我们与开发者们互动的方式:我们把他们当做客户与合作伙伴。

lazy.png

就像测试游戏那样,框架和类似过程都需要开发者们的反馈,更重要的是对这些反馈做出行动,建立信任并不断迭代。不同的开发者可能有不同程度的参与,但在《盗贼之海》和《我的世界》自动化测试当中,很多开发者做出了大量贡献。有些人只是给出反馈,还有人补充了他们团队需要的框架。

在人们对新工作方式不确定之后,担心这可能需要投入大量人手、甚至可能是浪费时间,我发现人们从最初的尝试者变成最后的积极推广者,最重要的转折点就是他们在自己的代码中找到了bug,这似乎是一个皆大欢喜的体验,转变了人们对自动化测试的态度。

我还认为,虽然测试框架现在看起来对我们是新鲜事物,但却是非常必要的工具,就像IDE或者编译器那样。我发现,不同的团队开始打造自己的测试框架,哪怕专门有团队是做测试框架的,我觉得这些团队的最终目标,就是帮助他们的客户最终实现不需要他们继续参与其中。

因此,我们团队很重要的一个活动,就是帮助这种升级的实现,用他们的知识和研发计划帮助社区打造。两个案例中都很好用的一个小技巧,就是测试审核者,他们知道如何进行测试,并且可以为其他人写代码。

6、领导层支持

领导层的支持可以成就或者破坏一个文化变革。在《盗贼之海》和《我的世界》项目中,我们很幸运有这种支持,我希望强调这种支持带来的一些帮助。

lazy.png

首先,我们的团队领导相信并成立了自动化测试团队,这是不常见的研发投入。他们还相信开发者投入时间写自动化测试的价值,这更多是一种信念,因为我们很难在当时证明这些价值。

即使有领导层支持,仍然会有意外发生,还需要注意预期管理。这有可能会延缓甚至迟滞研发进度,很容易带来自动化测试是否值得投入的问题,所以我觉得领导层理解并改变研发团队工作方式是很重要的,它会在短期内让研发变慢,但长期优势非常大。

我们还看到有些领导希望看到整体游戏品质提升的即时结果,基于我们的经验,我认为评估它是否有效比较合理的时间是一年。取决于你想要开始的代码数量,这可能会需要更长时间。如果你有大量的未测试代码,最好的方法就是先测试新代码和有bug的部分,而不是尝试给所有遗留代码做测试。

我看到另一个现象,就是将测试与研发分离,这通常意味着测试工作给更多新功能研发让路。我们将测试作为每个功能可分离的一部分,并且将其作为评估功能是否完成的一部分。事实胜于雄辩,如果领导层重视测试,却又想要看到短期效果,那么开发者就要将功能研发的优先级置于测试之前。当然,开发者们也需要做好与领导的沟通,告诉他们这种选择需要做出的变通,让领导听到反馈,然后给开发者们尝试的空间。

lazy.png

这是《我的世界》在2019年10月加入自动化测试之后的结果,三条线分别是单元测试、功能测试和服务器测试。可以发现,直到2020年4月左右服务器测试次数才开始增长,因为这时候刚好是我们与开发者互动,让写服务器测试变得更加简单。

随后,可以看到这些测试稳定增长,因为大部分开发者如今可以写他们大部分的测试工作,最近的开发者调查显示,我们的自动化测试框架比《我的世界》任何研发部分的好评率都更高,我觉得这说明了我们的努力是有成效的。

比较有趣的是,服务器测试似乎推动了单元测试,这是我没有想到的。我们认为,服务器非常便捷而且容易获得测试机巧,随后他们会喜欢真正的单元测试带来的帮助,继而会更有动力让系统适合单元测试。

然而,我们还有很长的路需要走,我们仍然有团队得不到框架的加持,有些规模化的难题还没有被解决,在《我的世界》项目上,我们的自动化测试才刚刚开始,但我们都对选择的方向感到乐观。

将我们的研发方法现代化是一个真正的挑战,但我相信自动化测试是控制游戏研发测试成本、避免未来过渡加班唯一的方式。

如若转载,请注明出处:http://www.gamelook.com.cn/2022/09/497805


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK