QA测试工作并不单调乏味,它是一项创造性的工作,蕴含着丰富的机会。公平地说,它也有枯燥乏味的一面--回归(regression)。因此,我们决定将回归测试自动化,具体方法如下。
在IT行业,有一种偏见,认为测试人员的工作既乏味又单调。我们不敢苟同,我们认为,质量保证既是一项创造性工作,也是一项技术性工作,它提供了全方位的研究机会。此外,要做好这项工作,需要沉浸在任务中,了解其所有的微妙之处和复杂性,并具备管理任何潜在陷阱的意识和能力。
auto-battler的机制自动化
首先,让我们介绍一下我们的工作流程所涉及的工作背景。我们负责开发一款名为《Hustle Castle》的游戏,这是一款兼具经济策略和 RPG 元素的移动自动战斗游戏。在这个项目中,Unity 是我们的首选引擎,服务器则是用 Java 编写的。自动战斗(auto-battle)的核心机制如下:玩家和敌方单位发生冲突,所有角色都有赋予能力的特殊装备,战斗自动继续--用户只能施法和使用英雄的天赋。
Hustle Castle 还有一个城堡功能(castle feature),其中包含无数房间,用户可以在这些房间中提取资源、制作物品等。此外,游戏中还有以部落、领地发展、竞技场等为特色的网络机制。所有这些都彼此共存,遵循着共同的逻辑。
完成了对 Hustle Castle 的介绍后,现在我们可以继续讨论更深层次的内容了。游戏玩法与单位的能力相关。从技术角度来看,能力是一种具有多种设置的实体:它如何以及何时被激活,它将影响谁以及如何影响,它还可以激活哪些其他能力等等。
我们以 JSON 对象的形式描述每个能力的行为。举例来说,能力可以是简单的、结构化的——它们也可以顺序或并行激活其他能力。此外,还有不同类型的能力,例如反击(counters)、增益(buffs)、眩晕(stuns)——一般来说,有很多不同的设置需要考虑。
所有这些都被自动战斗算法考虑在内。简而言之,它的工作原理是这样的:有一些数据作为输入——我们的战士的状态、他们拥有的统计数据和能力。我们将这些数据传输到战斗计算器(battle calculator),由它进行所有计算。对于每个计算步骤,它都会报告发生了什么事件、是否激活了某种能力以及是否造成了某些伤害。经过一个完整的循环计算,我们就得到了战斗的结果。
值得注意的是,战斗计算器代码在客户端和服务器端都可用。这对于验证战斗结果是必要的——客户端、服务器和战斗计算器需要查看相同的数据。
为了自动化自动战斗(auto-battle)检查,我们决定制作一个自定义客户端,它可以向服务器生成请求,以根据传入数据接收初始战斗状态。自动测试使用此客户端,接收状态,将其传递给计算器,订阅新计算步骤的事件,然后检查每个步骤的条件。
战斗系统的平均测试
为了检查战斗系统( combat system)的机制,我们使用专门为测试而收集的能力。检查针对的是机制,我们不检查制作过程中的技能。
我们还在名为 Battle Runner 的内部产品中使用了这种方法。我们的游戏设计师需要它来平衡战斗系统。游戏设计师可以从产品中提取任何玩家状态,将其组合成测试组,选择性地用一种能力替换另一种能力,然后在大量战斗中运行游戏。运行后,游戏设计者就能得到所需的战斗统计数据。
这是迈向自动化的第一步。我们的自动测试帮助我们确保战斗计算器的基本逻辑在游戏设计师和开发人员做出一些更改后仍然有效。这是一个复杂的过程,不幸的是,我们没有为此进行单元测试。因此,这些战斗计算器自动测试是我们确保大部分战斗系统稳定的唯一方法。
在服务器和客户端上进行测试
为了解释接下来会发生什么,让我们考虑一些理论--假设你正在观察众所周知的测试金字塔。
金字塔的本质很简单。我们越接近底部,测试就越便宜、越快;越高,测试的时间就越长,成本也越高。
解决方案似乎很明显——我们需要使用最快、最便宜的单元测试。但事情比这要复杂一些。为了让开发人员编写单元测试,应用程序必须具有特定的设计——也就是说,它必须是可测试的。不幸的是,并非所有这些都是可测试的。
服务器逻辑几乎没有单元测试,我们有的只是组件测试,主要包括匹配、模式和功能的基本逻辑。
如果我们单独考虑服务器,那么我们会遇到以下情况:我们的服务器上没有集成测试。理论上我们可以编写 API 测试,因为我们的客户端和服务器使用 protobuf 协议进行通信。因此,有了协议的描述,我们就可以获取客户端并发送请求。但目前,我们保留这个想法。
在客户端,事情就有点悲惨了。没有单元测试,也没有组件测试。因此,我们发现自己位于金字塔的顶端,只需通过 UI 测试我们的应用程序即可。我们的大多数游戏看起来都是这样的:很多按钮、对话框、弹出窗口、更多对话框、更多按钮。几乎所有界面元素都位于 Canvas 上。
作为基本工具,我们使用开源解决方案AltUnityTester(https://alttester.com/tools/),它是一个提供以下功能的驱动程序:
-
使用x路径搜索对象
-
场景管理
-
模拟输入法(点击、滚动、拖放等)
-
调用方法并获取游戏对象的属性
-
通过web socket进行交互的协议,允许添加许多其他命令。
因此,我们使用了 Java、Allure、TestNG,并决定应用页面对象模式并开始编写测试。起初,一切都很酷。我们编写了大约 10-15 个基本测试,只是检查界面是否执行了某些操作。
然而,很快我们就发现,我们的代码库包含许多问题,随着项目的不断发展,这些问题对我们的影响会越来越大。第一个问题与选择器(selectors)有关。下面的屏幕截图显示了我们如何使用页面对象的示例。类的字段是选择器,方法包含对驱动程序和附加逻辑的调用。
问题不仅在于其庞大的外观,还在于 AltUnity API 最终出现在我们所有的类中。如果开发人员要在新版本中更改某些内容,那么更新对我们来说将是极其痛苦的。
另一个问题是 Page 对象的责任。首先,在 Page 对象内部,我们调用驱动程序(hello, API!)。其次,对象可能有扭曲的逻辑。第三,我们的 Page 对象了解其他 Page 对象——也就是说,它们参与对象的导航。
我们的页面对象看起来像这样
另一个问题是依赖注入。在类很少的时候,一切都井然有序。但随着测试的复杂化,有必要连接大量的依赖关系,并牢记我们一般有哪些依赖关系。
这就是典型测试的依赖关系
大量的依赖关系会造成不必要的困难:例如,如果一个新人来到公司并尝试编写自动测试,他们将需要研究所有种类的 API,创建我们拥有的类以及它们如何使用的思维导图。是相关的,并付出很大的努力来深入研究这个过程。
测试看起来非常混乱,还很难理解
我们遇到的最后一个问题是代码重复。例如,上图显示了OpenShopAndBuyRoom方法,该方法是这个测试类私有的,所以我们不能在其他地方使用它。但由于我们想要编写更多测试,因此我们希望以某种方式重用此方法,并且它必须属于某个类。
是时候停下来思考了
AltUnityTester 和 Page 对象模式的使用与 Web 应用程序开发中的自动化非常相似。我们的同事使用 Selenium WebDriver。如果我们从网络上获取概念并在我们的主题领域中使用它们,那么我们就会得到以下结果:
1.UnityDriver:与游戏交互。
2.Unity-Object:用于描述对话框、屏幕和场景的结构模式。我们只用它们来描述结构,而 STEPS 则处理逻辑。
3.Unity-Element:按钮、图片、对话框、文本等。一般来说,Unity 中舞台上的一切都是 Unity 元素。
我们研究了 WebDriver 和 HTML Elements 框架的源代码,并设法根据我们的需要调整了代码。我们还使用 STEPS 模式将测试逻辑与 Unity-Objects 分离开来。结果,我们得到了一个框架,使我们能够:
1.将实体分组为单独的类(Button、Label、AbstractDialog 等)。
2.使用 @FindBy 注释设置 UI 元素的 x 路径,并引入新的注释和扩展。
3.创建单独的元素组并通过在另一个对象的上下文中搜索对象来在不同的对话框中重用它们。
4.在测试端在 Unity 中创建组件的表示(因为一个对象中可以有多个组件)。
5.使用 STEPS,根据游戏的业务逻辑编写测试(“开商店”、“购买产品”等)。
6.AltUnity代码深入核心,驱动程序隐藏在界面后面。
关于STEPS的一些信息:它们将我们的测试与 Unity-Objects 连接起来。Unity-Objects 使得点击元素或从游戏中传输一些数据成为可能,并且所有逻辑都在 STEPS 中。这使我们能够根据业务流程编写测试。例如,“在该位置,开设一个兵营”,“在兵营中,升级兵营”,“选取一个单位并将其转移到兵营”。在幕后,是拖放、点击和其他一切。
STEPS的第二个特点是它们可以在未来重复使用;并且不仅仅在功能测试的框架内。例如,最近我们需要在多种不同的玩家状态下实现一种场景的试运行。我们创建了一个新项目,使用 STEPS 激活库,使用几行代码来运行脚本 - 就这样完成了。
下面是 Unity 对象。还记得我们的选择器是什么样子的吗?他们非常丑陋。现在我们只使用注释来规定如何搜索所需的元素 - 就是这样。
类型化元素的示例
这就是我们项目中几乎所有对话的描述方式。同时,STEPS 可以访问可点击的按钮、重复对象的列表,并且 STEPS 还可以从整个对话中接收信息(我们有多少金币,哪些插槽打开或关闭等等)。
Unity 元素加载器(Unity Element Loader)负责初始化类字段 - 它接收特定的类和驱动程序。根据某种逻辑,我们为类中的每个字段创建代理元素。因此,我们可以简单地写成“自按按钮”,虽然实际上系统会先找到这个按钮,然后才会返回有关该按钮的信息,然后才完成“按下”命令。
下面可以看到我们有任务对话框的步骤。这些步骤已经根据游戏本身进行了描述。
测试示例
所有测试都是这样的:我们只使用一次 STEPS 注入。基于此,我们使用业务逻辑术语来描述我们想要做的一切——最终,一切看起来都非常整洁。
未来的计划
未来,我们的主要计划是进行更多测试。所有这些努力都是为了方便、简单且易于理解地扩展我们的代码库;但多线程的问题迫在眉睫。
目前,测试正在一个游戏实例的一个线程中运行。一切都很顺利,但这需要很长时间。
为了解决这个问题,我们可以在远程服务器上创建多个实例。或者我们可以组装一个设备群并连接到它们。但我们的一些功能是全局性的,可能会干扰测试:例如,如果农业门户是开放的,那么它就向所有人开放。在并行运行测试的界面中打开或关闭门户时,可能会出现通知,并且可能会点击此通知,而不是所需的元素。
我们想要实施的下一件事是 back-to-back testing 。这是指使用两个版本的应用程序,运行相同的脚本,在某个时间点截图,然后进行比较。这样,你就可以检查是否有什么地方出错了,是否有什么功能提前出现了等等。
目前,我们正在扩大功能覆盖范围,我们有一个 smoke set ,其中至少有一个针对游戏任何方面的测试,并且我们还开始培训同事编写测试。
测试自动化框架是一种产品。它应该简单、易懂、易于扩展。在设计时,应牢记软件开发的模式和原则,以及与重构相关的模式和原则。否则,摆脱回归将成为令人头疼的维护问题。
感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:
这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取