GameDev TDD或Rabbit Hell

TDD在游戏开发中很少使用。 雇用测试人员通常比让开发人员编写测试更容易-这样既节省了资源,又节省了时间。 因此,每次成功使用TDD都会变得更加有趣。 在剪辑之下,对材料进行翻译,并使用这种开发技术在游戏《 ElemenTerra》中创造角色的动作。



测试驱动的开发或TDD(通过测试进行的开发)是一种软件开发技术,其中整个过程分为许多小周期。 编写单元测试,然后编写通过这些测试的代码,然后进行重构。 并且算法重复。

TDD基础


假设我们编写了一个将两个数字相加的函数。 在正常的工作流程中,我们只是编写它。 但是要使用TDD,您需要先创建一个占位符函数和单元测试:

// Placeholder-,    : int add(int a, int b){ return -1; } // Unit-,   ,  add    : void runTests(){ if (add(1, 1) is not equal to 2) throw error; if (add(2, 2) is not equal to 4) throw error; } 

首先,我们的单元测试将不起作用,因为占位符函数为每个输入返回-1。 现在我们可以正确执行add来返回a + b 。 测试将通过。 这似乎是一种解决方法,但是有几个优点:

如果我们错误地将add写为a-b ,我们的测试将无法正常工作,我们将立即学习如何修复该功能。 如果没有测试,我们将无法捕获此错误,并且会看到非标准的反应,这需要花费一些时间进行调试。
我们可以继续测试并在编写代码时随时运行它们。 这意味着,如果另一个程序员不小心更改了add ,他将立即识别该错误-测试将再次失败。

游戏开发中的TDD


游戏开发中的TDD存在两个问题。 首先,许多游戏功能具有无法衡量的主观目标。 其次,很难编写涵盖了充满复杂交互对象的世界所有可能性的测试。 希望角色的动作“看起来不错”或物理模拟“不要看起来生涩”的开发人员会发现,很难将这些指标表示为确定的“通过/未通过”条件。

但是,TDD技术适用于复杂和主观的功能-例如角色移动。 在ElemenTerra游戏中,我们做到了。

根据调试级别进行单元测试


在开始练习之前,我想区分自动单元测试和传统的“调试级别”。 在人工开发条件下创建隐藏位置在gamedev中很常见。 这使程序员和QA可以监视单个事件。


《塞尔达传说:风行者》中的秘密调试级别

ElemenTerra有许多这样的级别:一个充满角色角色几何形状的级别,一个具有触发某些游戏状态的特殊用户界面的级别。

像单元测试一样,这些调试级别可用于重现和诊断错误。 但是它们在某些方面有所不同:

单元测试将系统分为多个部分,分别进行评估,而调试级别则以更全面的方式进行测试。 在调试级别发现错误后,开发人员可能仍需要手动搜索错误点。
单元测试是自动化的,每次必须提供确定性的结果,而许多调试级别则由播放器“控制”。 这使会话有所不同。

但这并不意味着单元测试比调试级别更好。 后者通常更实用。 但是,单元测试甚至可以在传统上不存在的系统上使用。

欢迎来到兔子地狱


在ElemenTerra,玩家可以利用自然界的神秘力量拯救受太空风暴影响的生物。 一种这样的力量是为将生物带到食物和庇护所铺平道路的能力。 由于这些路径是玩家创建的动态网格,因此该生物的动作必须处理异常的几何情况和任意复杂的地形。

角色移动是“一切都影响其他一切”的复杂系统之一。 如果您这样做过,就会知道编写新代码时,很容易破坏现有功能。 您是否需要兔子爬小壁架? 好的,但是现在他们在抽搐,爬上山坡。 您是否希望蜥蜴路径不相交? 它起作用了,但是现在他们的典型行为被破坏了。

作为负责AI和大多数游戏代码的人员,我知道我没有时间来解决意外错误。 我想立即注意到回归,因此使用TDD进行开发对我来说似乎是一个不错的选择。

下一步是创建一个系统,在该系统中,我可以通过模拟通过/失败测试的形式轻松识别每个运动情况:



这个“兔子地狱”由18条孤立的走廊组成。 每个生物都有自己的生物和自己的路线,旨在仅在某些运动功能起作用时才运动。 如果兔子能够无限制地移动无限长的时间,则测试被认为是成功的。 否则,不会成功。 请注意,我们仅测试生物的身体(以虚幻的术语表示为典当),而不是人工智能。 在ElemenTerra中,生物可以吃东西,睡觉和对世界做出反应,但是在“兔子地狱”中,他们唯一的指示是在两点之间奔跑。

以下是此类测试的一些示例:


1,2,3:自由运动,静态障碍物和动态障碍物


8和9:均匀的坡度和崎rough的地形


10:消失的地板


13:重现动物不断围绕附近目标旋转的错误


14和15:能够浏览平坦和复杂的壁架

让我们谈谈我的实现与“干净的” TDD之间的异同。

我的系统在以下方面类似于TDD:

  • 我开始通过创建测试来处理函数,然后编写了运行它们所需的代码。
  • 我继续运行旧的测试,添加了新功能。
  • 每个测试仅测量系统的一部分,这使我能够快速发现问题。
  • 测试是自动化的,不需要玩家输入。

并在以下方面有所不同:

  • 在评估测试时,有一个主观因素。 虽然可以通过程序检测到真正的移动错误(角色没有从A到B)。 也就是说,例如,偏斜位置,动画的同步和抽动运动的问题需要人工评估。
  • 测试不是完全确定的。 诸如帧速率波动之类的随机因素会导致较小的偏差。 但是一般而言,生物通常遵循相同的路径,并且在会话之间具有相同的成功/失败。

局限性


使用TDD来移动ElemenTerra生物是一个巨大的优势,但是我的方法有几个局限性:

  • 单元测试分别评估了机芯的每个特征,因此没有考虑多个特征组合的错误。 有时有必要用传统的调试级别补充单元测试。
  • ElemenTerra有四种生物,但测试只包含兔子。 这是我们生产计划的一个功能(其他三种类型在开发的后期添加了)。 幸运的是,这四个人都具有相同的机动性,但是Mossmork的大身材造成了几个问题。 下次,我将使测试动态生成选定的物种,而不是使用预先放置的兔子。


这个Mossmork比兔子需要更多的空间。

您选择TDD吗?


开发人员可能会在单元测试级别上花费过多的精力,而这是玩家永远不会喜欢的。 我并不否认,我自己从创造“兔子地狱”中获得了很多乐趣。 这样的内部功能可能很耗时,并且会危害更重要的里程碑。 为了防止这种情况的发生,请仔细研究在何时何地使用单元测试。 在下文中,我重点介绍了一些标准,这些标准证明TDD对于ElemenTerra生物的移动是合理的。

1.手动完成测试任务会花费很长时间吗?

在花时间进行自动测试之前,您需要检查我们是否可以使用常规游戏控件评估该功能。 如果要确保您的钥匙将门解锁,请生成钥匙并为其打开门。 为此功能创建单元测试将浪费时间-手动测试仅需几秒钟。

2.手动创建测试用例难吗?

如果存在已知且难以复制的案例,则可以进行自动单元测试。 “兔子地狱”的第7号测试检查它们如何沿着壁架行走-AI通常会努力避免这种情况。 使用游戏控件可能很难或不可能重现这种情况,并且测试很容易。

3.您知道预期的结果不会改变吗?

游戏设计完全基于迭代,因此功能的目标会随着游戏的改变而改变。 即使很小的更改也会使您评估功能所用的度量标准无效,从而使任何单元测试无效。 如果这些生物在食物,睡眠和与玩家互动过程中的行为发生了数次变化,则从A点到B点的过渡保持不变。 因此,运动代码及其单元测试在整个开发过程中仍然很重要。

4.回归是否有可能被忽视?

在发送游戏之前完成最后一项任务,突然发现违反规则的错误时,您是否遇到这种情况? 并在您多年前完成的功能中。 游戏是巨大的互连系统,因此添加新功能B很自然会导致旧功能A失败。

当到处都使用损坏的功能(例如,跳转)时,情况还算不错-您应该立即注意到机械故障。 在以后的开发中发现的错误可能会破坏进度,并且在发布后可能会损害游戏玩法。

5.如果不使用测试,可能会发生最糟糕的情况?

创建测试是风险管理的一种形式。 想象一下,您决定是否购买车辆保险。 您需要回答三个问题:

  • 每月保险费是多少?
  • 汽车损坏的可能性有多大?
  • 如果您没有保险,最坏的情况会有多昂贵?

对于TDD,我们可以想象以每月费用的形式提供服务于单元测试的生产成本,以损坏的可能性的形式将汽车损坏的可能性以及完全更换汽车的成本作为回归误差的最坏情况。

如果创建功能测试花费大量时间,那么它很简单并且不太可能更改(或者如果在以后的开发中遇到问题可以解决),那么单元测试可能会带来更多的问题。 如果测试容易进行,功能不稳定且相互连接(否则其错误将花费大量时间),则测试会有所帮助。

自动化限制


单元测试可以作为发现和消除错误的重要补充,但不能代替大型游戏中专业质量控制的需求。 质量检查是一门需要创造力,主观判断力和出色技术交流的艺术。

Source: https://habr.com/ru/post/zh-CN455076/


All Articles