测试驱动开发是游戏开发中的常规方法吗?

时间:2009-03-06 17:10:11

标签: tdd

我很好奇,因为我看到的所有TDD示例都与网络编程相关。如果这不是一种正常的方法,为什么不呢?

8 个答案:

答案 0 :(得分:54)

TDD已成为认真对待其专业的软件开发人员的青睐方法。 [IEEE:TDD]该方法的好处是显着的,相比之下成本较低。 [The Three Laws of TDD]

没有TDD不合适或无效的软件域。但是,有一些领域具有挑战性。游戏恰好是其中之一。

实际上,挑战不是游戏,而是UI。 UI面临挑战的原因是,在您看到UI之前,您通常不知道UI的外观。用户界面是你必须摆弄的东西之一。正确的做法是一个深刻的迭代过程,充满曲折,死胡同和后巷。为UI编写首先的测试可能既困难又浪费。

现在每个人都咆哮起来说:“鲍勃叔叔说:'不要为用户界面制作TDD'”让我这样说。难以为UI做纯TDD的事实并不意味着你几乎不可能为其他所有东西做纯TDD。大部分游戏都是关于算法的,你可以将TDD与这些算法结合使用,让你心满意足。确实如此,特别是在游戏中,其中一些算法就是你必须使用的那种代码,就像UI一样,因此可能不适合进行首先的测试。但是还有很多其他的算法代码可以而且应该首先编写测试。

这里的诀窍就是遵循single responsibility principle (SRP)并将那些你必须摆弄的代码与那些具有确定性的代码分开。不要在UI中放入易于测试的算法。不要将您的推测代码与非推测性代码混合在一起。 保留因理由而改变的事物与因理由B而改变的事物分开。

另外,请记住这一点:某些代码很难先测试 ,这并不意味着此代码很难测试 second 。一旦你摆弄和调整并让代码按你喜欢的方式工作,然后你可以编写测试证明代码的工作方式与你的想法一致。 (在执行此操作时,您会发现错误的次数,您会感到惊讶。)

“事后”编写测试的问题在于,代码经常是如此耦合,以至于很难编写最有用的手术测试。因此,如果您正在撰写难以测试首先的代码,则应注意遵循dependency inversion principle (DIP)open/closed principle(OCP)以保持代码解耦足以在事后进行测试。

答案 1 :(得分:19)

简单的答案是“不”,TDD不是游戏开发中的常规方法。有些人会指出Highmoon和Neil Llopis作为反例,但这是一个很大的行业,他们是我所知道的唯一完全接受TDD的人。我确信还有其他人,但他们是我所知道的唯一(我已经在这个行业工作了5年)。

我认为我们很多人已经在某个时候涉足单元测试,但由于某种原因,它还没有成功。从个人经验来看,游戏工作室很难转向TDD。通常,代码库从一个项目到另一个项目保持不变,并且将TDD应用于大型现有代码库既繁琐又基本上没用。我敢肯定,最终它会证明是富有成效的,但让游戏编码员购买它很困难。

我在为低级游戏引擎代码编写单元测试方面取得了一些成功,因为这些代码往往具有很少的依赖性并且很容易封装。这一直是在事实之后进行测试,而不是TDD。更高级别的游戏代码通常更难编写测试,因为它具有更多的依赖性,并且通常与复杂的数据和状态相关联。以AI为例,测试AI需要某种上下文,这意味着导航网格和世界上的其他对象。单独设置这种测试可能并非易事,特别是如果所涉及的系统不是为它而设计的。

在游戏开发中更常见的是,我有更多的个人成功,是烟雾测试。您经常会看到烟雾测试与持续集成结合使用,以提供有关代码行为的各种反馈。烟雾测试更容易,因为它可以通过将数据输入游戏并回读信息来完成,而无需将代码划分为微小的可测试部分。再次以AI为例,您可以告诉游戏加载一个级别并提供一个脚本来加载AI代理并为其提供命令。然后,您只需确定代理是否执行这些命令。这是一个冒烟测试,而不是单元测试,因为你是整个游戏而不是单独测试AI系统。

在我看来,通过对低级代码进行单元测试,同时对高级行为进行测试,可以获得不错的测试覆盖率。我认为(希望)其他工作室也采取类似的方法。

如果我对TDD的看法听起来有些含糊不清,那是因为它。关于它,我仍然有点蠢蠢欲动。虽然我看到了一些好处(回归测试,在代码之前强调设计),但在使用预先存在的代码库时应用它并强制执行它似乎是一个令人头痛的秘诀。

答案 2 :(得分:10)

来自Within的游戏an article讨论了他们对单元测试的使用,特别是游戏的单元测试的局限性,以及他们为帮助实现此目的而设置的自动化功能测试服务器。

答案 3 :(得分:5)

如果你指的是为每一段代码编写和维护单元测试的做法,我冒昧地猜测并说明这在游戏行业中并没有广泛使用。这有很多原因,但我可以想到3个显而易见的原因:

  • 文化。程序员是保守的,游戏程序员更是如此。
  • 实践。 TDD不适合问题域(运动部件太多)。
  • Crunchological。从来没有足够的时间。

TDD范例在应用程序域中效果最好,这些应用程序域不是非常有状态,或者至少在移动部件不是同时移动的情况下,通俗地说。

TDD适用于游戏开发过程的一部分(基础库等),但这项工作中的“测试”通常意味着运行自动飞行,随机密钥测试,定时加载,跟踪fps峰值,确保玩家不能扭曲他的方式导致视觉不稳定,像这样的东西。自动机也经常是人形机器人。

TDD可以是一个有用的工具,但它作为一个必须无处不在的银弹的地位 - 在制造系统时是相当可疑的。发展不应该由测试驱动,而是由理性驱动。 RDD虽然是一个糟糕的缩写 - 它不会流行起来。 ;)

答案 4 :(得分:2)

可能主要原因是TDD更受那些语言更有利的人的青睐。但除此之外,无论如何,游戏本身与范式相匹配。

一般来说(是的,我的意思是一般来说,所以请不要用反例来淹没我),测试驱动的设计最适合事件驱动的系统而不是模拟式系统。您仍然可以在游戏中使用低级别组件的测试,无论是测试驱动还是纯粹的单元测试,但对于更高级别的任务,很少有任何类型的离散事件可以用确定性结果进行模拟。

例如,Web应用程序通常具有非常不同的输入(HTTP请求),更改非常少量的状态(例如,数据库中的记录),并生成很大程度上确定性的输出(例如,HTML页面) 。这些可以很容易地检查有效性,因为生成输入很简单,所以创建测试是微不足道的。

然而,对于游戏,输入可能难以模拟(特别是如果它需要在某一点发生...想想通过加载屏幕,菜单屏幕等),你改变的状态量可能很大(例如,如果你有物理系统,或复杂的反应性AI)并且输出很少是确定性的(随机数使用是这里的主要罪魁祸首,尽管浮点精度损失之类的东西是另一种,可能是硬件规格,或者可用CPU时间,或后台线程的性能等。)。

要做TDD,您需要确切地知道您希望在某个事件中看到的内容并准确地测量它,并且这两个都是模拟避免离散事件的困难问题,故意包括随机因素,行为在不同的机器上有不同的功能,并具有模拟输出,如图形和音频。

此外,还有一个重大的实际问题,即流程启动时间。您要测试的许多内容都需要加载大量数据,如果您模拟数据,则不会真正测试算法。考虑到这一点,拥有任何只执行单个任务的测试脚手架很快就变得不切实际。您可以针对Web服务器运行测试,而无需每次都关闭Web服务器 - 除非您使用嵌入式脚本语言进行测试(这是合理的,并且确实在行业中进行),否则游戏很少会出现这种情况。 p>

例如,您希望为游戏中的建筑物添加体积阴影渲染。因此,您需要编写一个测试,启动所有必要的子系统(例如,渲染器,游戏,资源加载器),加载建筑物(包括网格,材料/纹理),加载摄像机,将摄像机定位到指向建筑物,启用阴影,渲染场景,然后以某种方式决定阴影是否实际出现在帧缓冲区中。这不太实际。实际上,你已经以游戏的形式拥有了所有这些脚手架,除了代码本身中的任何断言之外,你只需启动它进行可视化测试。

答案 5 :(得分:1)

大多数游戏开发者在现代开发实践方面并不完全适应它。值得庆幸的是

但是,测试驱动的开发模型强调专注于如何首先使用某些东西,然后充实它的功能。这通常是好事,因为它迫使你专注于某个特定功能如何实际适合你正在做的事情(比如游戏)。

如此优秀的游戏开发者自然会这样做。只是不明确。

答案 6 :(得分:1)

@Rune再一次,请强调'D'而不是'T'。在单元级别,测试是一种思考工具,可帮助您了解所需内容以及驱动代码设计。当然在单元级别,我发现我最终得到了更干净,更健壮的代码。我放入系统的部件的质量越好,它们就越少,(但不是没有)错误。

这与游戏所需的严肃测试完全不同。

答案 7 :(得分:0)

TDD在任何地方都不是真正的“正常”方法,因为它仍然是相对较新的,尚未得到普遍理解或接受。这并不是说现在有些商店不会这样工作,但我现在仍然会听到有人使用它。