Unity中如何自动化回归测试

在IT行业中,有一种偏见认为测试人员的工作既枯燥又单调。我们对此表示不同意。在我们看来,QA既是一项创造性的工作,也是一项技术性的工作,它提供了一系列的研究机会。此外,要做好这项工作,你需要全身心投入,理解其中的所有细微之处和复杂性,并具备意识和能力来应对任何潜在的陷阱。

自动化自动战斗的机制

首先,让我们提供一些关于我们工作流程所适用的工作背景。我们负责开发一款名为Hustle Castle的游戏,这是一款移动自动战斗游戏,融合了经济策略和RPG元素。Unity是我们的首选引擎,服务器是用Java编写的。自动战斗的核心机制如下:玩家和敌方单位发生冲突,所有角色都拥有赋予能力的特殊装备,战斗自动进行——用户只能施放法术和使用英雄的天赋。

Hustle Castle还拥有一个城堡功能,其中包含无数房间,你可以在这些房间中提取资源、制作物品等等。此外,还有一些网络机制,包括氏族、领地开发、竞技场等等。所有这些都相互共存,遵循共同的逻辑。

在介绍完Hustle Castle之后,现在我们可以深入探讨一些更深层次的东西。游戏玩法与单位的能力息息相关。从技术角度来看,能力是一种实体,它拥有许多设置:如何以及何时激活、影响谁以及如何影响、可以激活哪些其他能力等等。

我们以JSON对象的格式描述每种能力的行为。举例来说,能力可以很简单,也可以很复杂——它们还可以顺序或并行激活其他能力。此外,还有不同类型的能力,例如反击、增益、眩晕——总的来说,需要考虑很多不同的设置。

所有这些都被自动战斗算法所考虑。简单来说,它的工作原理是这样的:输入一些数据——我们战斗人员的状态、他们的属性和能力。我们将这些数据传输到战斗计算器,它会进行所有计算。对于每个计算步骤,它都会报告发生了什么事件,是否激活了某种能力,以及是否造成了伤害。经过一个完整的计算周期后,我们得到战斗结果。

图片

 

值得注意的是,战斗计算器代码在客户端和服务器端都有。这是为了验证战斗结果——客户端、服务器和战斗计算器需要查看相同的数据。

为了自动化自动战斗检查,我们决定制作一个自定义客户端,它可以生成一个请求发送到服务器,以接收基于传入数据的初始战斗状态。自动测试使用这个客户端,接收状态,将其传递给计算器,订阅新的计算步骤事件,然后在每个步骤检查条件。

图片

我们战斗系统的平均测试

为了检查战斗系统的机制,我们使用专门用于测试的技能。检查的目标是机制;我们不检查生产中的技能。

我们还在我们内部产品Battle Runner中使用了这种方法。我们的游戏设计师需要它来平衡战斗系统。游戏设计师可以从生产中获取任何玩家状态,将它们组合成测试组,可以选择用另一种技能替换一种技能,并在大量战斗中运行游戏。运行的结果是,游戏设计师获得了所需的战斗统计数据。

这是迈向自动化的第一步。我们的自动测试帮助我们确保在游戏设计师和开发人员进行了一些更改后,战斗计算器的基本逻辑仍然有效。这是一个复杂的过程,不幸的是,我们没有为此进行单元测试。因此,这些战斗计算器自动测试是我们确保大部分战斗系统稳定的唯一方法。

服务器和客户端上的测试

为了解释接下来会发生什么,让我们考虑一些理论——假设你在观察著名的测试金字塔。

图片

 

金字塔的本质很简单。我们越靠近底部,测试就越便宜、越快;我们越高,测试就越长、越贵。

解决方案似乎很明显——我们需要使用最便宜、最快的单元测试。但事情比这复杂一些。为了让开发人员编写单元测试,应用程序必须具有特定的设计——也就是说,它必须是可测试的。不幸的是,并非所有应用程序都是可测试的。

服务器逻辑几乎没有单元测试,而我们现有的单元测试是组件测试,主要涵盖匹配机制以及模式和功能的基本逻辑。

如果我们单独考虑服务器,那么我们有以下情况:我们的服务器上没有集成测试。理论上我们可以编写API测试,因为我们的客户端和服务器使用protobuf协议进行通信。因此,有一个协议描述,我们可以使用客户端发送请求。但目前,我们暂时保留这个想法。

在客户端方面,情况更加糟糕。既没有单元测试,也没有组件测试。因此,我们发现自己处于金字塔的顶端,我们只能通过UI测试我们的应用程序。我们的大部分游戏都是这样的:很多按钮、对话框、弹出窗口、更多对话框、更多按钮。几乎所有界面元素都位于Canvas上。

作为一个基本工具,我们使用开源解决方案AltUnityTester
(https://altom.com/testing-tools/altunitytester/)——这是一个驱动程序,它提供:

  1. 使用x-path搜索对象

  2. 场景管理

  3. 模拟输入方法(点击、滚动、拖放等等)

  4. 调用方法并获取游戏对象属性

  5. 通过网络套接字进行交互的协议,允许你添加许多其他命令。

图片

 

因此,我们使用了Java、Allure、TestNG,并决定应用Page-Object模式,开始编写测试。起初,一切都相当不错。我们编写了大约10-15个基本测试,它们只是检查界面是否执行了某些操作。

然而,很快我们就发现我们的代码库存在一些问题,随着项目的不断发展,这些问题会对我们造成越来越大的影响。第一个问题与选择器有关。下面的截图展示了我们使用Page-Object的方式。类的字段是选择器,方法包含对驱动程序的调用以及额外的逻辑。

图片

 

问题不仅在于它庞大的外观,还在于AltUnity API最终出现在我们所有的类中。如果开发人员在新版本中更改了一些内容,那么我们更新起来将是痛苦的。

另一个问题是Page对象的职责。首先,在Page对象内部,我们调用了驱动程序(你好,API!)。其次,对象可能具有扭曲的逻辑。第三,我们的Page对象了解其他Page对象——也就是说,它们参与了对象之间的导航。

图片

我们的Page对象看起来像这样

另一个问题是依赖注入。当类很少时,一切都很正常。但是随着测试的复杂化,需要连接一堆依赖项,以及记住我们通常有哪些依赖项。

图片

这是典型测试的依赖项的样子

大量的依赖项会导致不必要的困难:例如,如果一个新人加入公司并尝试编写自动测试,他们需要学习所有API,创建我们拥有的类以及它们之间关系的心理地图,并付出很多努力才能深入了解这个过程。

图片

测试看起来非常混乱;很难理解它们

我们遇到的最后一个问题是代码重复。例如,上面的图片显示了OpenShopAndBuyRoom方法,它是这个测试类的私有方法,因此我们无法在其他地方使用它。但是,由于我们想编写更多测试,因此我们希望以某种方式重用此方法,它必须属于某个类。

是时候停下来思考了

使用AltUnityTester和Page对象模式与Web应用程序开发中的自动化非常相似。在那里,我们的同事使用Selenium WebDriver。如果我们从Web中获取概念并将其应用到我们的主题领域,那么我们将得到:

  1. UnityDriver——与游戏的交互。

  2. Unity-Object——用于描述对话框、屏幕和场景的结构模式。我们只使用它们来描述结构,STEPS处理逻辑。

  3. Unity-Element——按钮、图片、对话框、文本等等。总的来说,我们在Unity舞台上拥有的所有东西都是Unity-Element。

我们研究了WebDriver和HTML Elements框架的源代码,并设法将代码适应我们的需求。我们还使用了STEPS模式将测试逻辑与Unity-Objects分离。因此,我们得到了一个框架,它允许我们:

  1. 将实体分组到单独的类中(Button、Label、AbstractDialog等等)。

  2. 使用@FindBy注释设置UI元素的x-path,以及引入新的注释和扩展。

  3. 创建单独的元素组,并在不同的对话框中重用它们,方法是在另一个对象的上下文中搜索对象。

  4. 在测试端创建Unity中组件的表示(因为一个对象中可能有多个组件)。

  5. 使用STEPS,以游戏的业务逻辑编写测试(“打开商店”、“购买产品”等等)。

  6. AltUnity代码位于核心深处,驱动程序隐藏在接口后面。

关于STEPS的几点说明:它们将我们的测试与Unity-Objects连接起来。Unity-Objects使我们能够点击元素或从游戏中传输一些数据,所有逻辑都在STEPS中。这使我们能够以业务流程的方式编写测试。例如,“在这个位置,打开一个兵营”、“在兵营中,升级兵营”、“带一个单位并将其转移到兵营”。在幕后,则是拖放、点击等等。

STEPS的第二个特点是,它们可以在未来重用;不仅在功能测试的框架内。例如,最近我们需要在许多不同的玩家状态上实现一个试运行。我们创建了一个新项目,激活了带有STEPS的库,使用几行代码运行脚本——就完成了。

下面是Unity Object。还记得我们的选择器是什么样子吗?它们非常丑陋。现在我们只需要使用注释,在注释中指定如何搜索所需的元素——就完成了。

图片

类型化元素的示例

这是我们项目中几乎所有对话框的描述方式。同时,STEPS可以访问可点击的按钮、重复对象的列表,STEPS还可以从整个对话框中获取信息(我们有多少金币、哪些插槽是打开的还是关闭的等等)。

Unity Element Loader负责初始化类字段——它接收一个特定的类和驱动程序。根据一些逻辑,我们为类中的每个字段创建代理元素。因此,我们可以简单地编写“Self-press button”,尽管实际上系统首先会找到这个按钮,关于此的信息会返回,只有在之后才会执行“Press”命令。

在下面,你可以看到我们为任务对话框创建了STEPS。这些步骤已经用游戏本身的术语描述了。

图片

测试示例

所有测试看起来都是这样:我们只使用一次STEPS的注入。基于此,我们使用业务逻辑的术语描述我们想要做的一切——最终,一切都看起来很整洁。

未来计划

未来,我们的主要计划是进行更多测试。所有这些努力都是为了方便、简单和易于扩展我们的代码库;但多线程问题正在逼近。

目前,测试在一个线程中为一个游戏实例运行。一切运行良好,但这需要很长时间。

为了解决这个问题,我们可以在远程服务器上创建多个实例。或者我们可以组建一个设备农场并连接到它们。但我们的一些功能是全局的,可能会干扰测试:例如,如果“耕作门户”是打开的——那么它对每个人都是打开的。在并行运行的测试的界面中打开或关闭门户时可能会出现通知,并且可能会点击通知而不是所需的元素。

我们想要实现的下一件事是背靠背测试。这是指你获取两个版本的应用程序,运行相同的脚本,在某个时刻截取屏幕截图,然后进行比较。因此,你可以检查是否出现了一些错误,是否过早地出现了某个功能等等。

目前,我们正在扩展功能覆盖范围,我们有一个烟雾测试集,其中至少包含一个针对游戏任何方面的测试,我们也开始培训同事编写测试。

测试自动化框架是一个产品。它应该简单、易懂、易于扩展。在设计它时,你应该记住软件开发的模式和原则,以及与重构相关的模式和原则。否则,消除回归将成为一个维护难题。

想了解更多游戏开发知识,可以扫描下方二维码,免费领取游戏开发4天训练营课程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值