单元测试 – TDD和游戏物理

我正在玩一个小游戏项目,因为我在TDD方面不是很有经验,所以我喜欢在一些事情上得到一些专家意见.

首先,我很早就意识到TDD似乎不适合游戏开发.看来这个问题的观点差异很大.我最初没有受过教育的意见是,TDD似乎对所有游戏逻辑都有效.我认为任何处理视频输出和声音的内容都会被抽象为可视化测试的类.

事情开始很顺利.目标是创建一个2d太空飞行游戏(小行星为那些关心的人).我为Ship类创建了一系列单元测试.像初始化,旋转这样的东西很容易在一系列中测试,例如:GetRotation(),TurnRotateRightOn(),Update(1),GetRotation(),Expect_NE(rotation1,rotation2).然后我遇到了第一个问题.

我对TDD的理解是你应该写测试你认为应该如何使用这个类.我希望船能够移动,所以我写了一个基本上说的课程. GetCoordinates(),ThrustOn(),GetCoordinates().那可以确保船在某处移动.但是,我很快意识到我必须确保船舶以正确的方向和正确的速度移动.接下来是一个75线单元测试,我基本上必须初始化旋转,检查坐标,初始化推力,更新船,获得新坐标,检查新的旋转.更重要的是,我认为没有必要在游戏中获得船的速度(只是坐标,船应该更新自己).因此,我没有直接获得速度的方法.所以测试基本上必须重新计算速度应该是什么,所以我可以确保它与更新后得到的坐标相匹配.总而言之,这是非常混乱,非常耗时,但工作.测试失败,测试通过等.

直到后来我才意识到我想将船的更新代码重构为抽象的“Actor”类时,这很好.我意识到虽然Actor的每个子类都需要能够正确计算一个新位置,但并不是每个子类都必须以相同的方式更新它们的速度(一些碰撞,一些不碰撞,一些碰撞静态速度).现在,我基本上面临着重复和改变庞大而庞大的测试代码的前景,我不禁想到应该有更好的方法.

有没有人有经验处理单元测试这种复杂的黑盒子类型的工作?看起来我基本上不得不在测试中编写完全相同的物理代码,所以我知道结果应该是什么.这真的是自我失败,而且我确信我一路上都错过了这一切.我非常感谢任何人提供的任何帮助或建议.

我建议你首先创建一个组件,在给定一系列控制输入的情况下计算位置和方向.然后该组件构成用于测试目的的“单元”.该组件的测试用例将执行您可以想到的所有场景:零加速,恒定非零加速,脉冲加速命令等.如果应用程序不需要速度,则组件不会暴露任何功能与速度有关.

在生成包含在测试中的预期输出时,重要的是要高度确信这些预期结果是正确的.因此,需要最小化生成预期结果所需的代码量.特别是,如果
你发现自己编写的测试脚手架几乎和测试中的组件一样复杂,那么出现测试本身的bug的前景就成了一个严重的问题.

在这种情况下,我将直接从运动方程生成测试数据.我使用Mathematica来实现这个目的,因为我可以直接输入方程式,解决它们,然后生成结果的图形和表格.这些图表让我可以看到结果,从而确信它们是可靠的. Excel / OpenOffice / Google Apps可用于相同的目的,以及像Sage这样的Mathematica的开源替代品.无论选择什么,关键的问题是能够解决运动方程而无需编写非平凡的代码.

一旦我们有一组好的测试用例以及预期的结果,我们就可以对单元测试进行编码.请注意,测试代码非常简单,本身不执行任何计算.它只是将组件的输出与我们之前获得的硬编码结果进行比较.在测试用例到位后,我们可以编写组件本身,添加代码直到测试全部通过.当然,在严格的TDD中,这些动作恰好按此顺序发生.我承认,我并不亲自坚持瀑布,而是倾向于在创建测试数据,编写测试和编写组件代码之间来回反弹.

Mathematica或Excel文档本身在初始创建测试之后具有使用寿命.添加新功能时可以再次使用它们,或者(天堂禁止)以后可以找到错误.我主张像处理源代码一样处理文档.

在本练习结束时,结果是一个“防弹”组件,我们确信自己将在任何给定的控制输入集合下计算对象的正确位置和方向.在此基础上,我们可以构建使用该功能的更多组件,例如船只,小行星,碟子和镜头.为了避免每个组件的测试用例的组合爆炸,我将偏离严格的黑盒测试方法.因此,例如,如果我们测试“ship”组件,我们会编写测试,知道它使用位置/方向组件.使用这种白盒知识,我们可以避免重新测试与运动相关的所有角落情况.船舶单元测试可以执行“烟雾测试”,以验证船舶实际上是否响应控制输入,但主要重点是测试船舶部件本身特有的任何功能.

所以,总结一下:

>使位置/方向计算成为单个组件的责任>使用外部工具为一组全面的测试用例生成数据,从而高度确信数据是正确的>避免测试本身的复杂计算逻辑>使用白盒知识躲避组件层次结构中测试用例的组合爆炸

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结