游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

测试驱动开发方法可提升整体游戏开发速度

发布时间:2012-02-17 15:23:36 Tags:,,

作者:Alistair Doulin

我们刚刚实现了完全用测试驱动开发(游戏邦注:Test Driven Development,下文简称“TDD”)方法制作完成首款游戏。我将在下文中分享我的经验,现在我们已经彻底完成了项目的首个版本。我之前讨论过测试驱动游戏开发方法(游戏邦注:下文简称“TDGD”),但是大部分是理论内容。所以,今天我想要分享的是TDGD如何有助于完成《Battle Group》的制作。

battle group(from m.appspy.com)

battle group(from m.appspy.com)

测试驱动游戏开发方法

测试驱动游戏开发方法背后的理念是,编写自动化单元测试来确认产品代码的正确与否。单元测试可以用来测试游戏的所有层面,从游戏玩法到渲染引擎及其他的内容。正如我之前所说的那样,TDGD有3个主要目标:

1、找到破坏性代码。如果你变更的代码会破坏某些之前已经能够运转的东西,你马上就会察觉到。

2、促成代码模块化。要使代码能够进行“单元测试”,就必须将其模块化,不同系统间有着清晰的边界。

3、让你“找到趣味性”。确保满足某些要求之后,你可以自由地改良游戏玩法,使之更具娱乐性,同时不会破坏整个游戏。这也会让设计师在编写脚本时更加自由。鼓励设计师脱离程序员来开展游戏玩法试验,这种做法的好处是不可估量的。

方法步骤

通常情况下,新游戏玩法功能的开发需遵循以下步骤:

1、为新功能设计单元测试,测试系统其他部分可见的外部界面

2、尽快执行功能,根据需要消除部分内容,这样才能够尽快地让该功能生效

3、团队对功能进行测试,确保其能够运转且有趣,我们通过重复测试来改良这个功能

4、对功能感到满意后,我会折回代码,清理所有捷径,使之成为最优化解决方案

这个系统极为有效,使新功能制作以及现有功能的维护和更新能够在短时间内完成。

游戏玩法测试

为《Battle Group》创建的所有单元测试的目标都是测试游戏玩法代码,因为这是我们游戏中最复杂的部分。Unity帮助实现了多数基于技术的系统(游戏邦注:比如渲染和输入),这使得我们项目中将近90%的代码的直接关联对象是游戏玩法。进行这种测试的主要动机在于,让我们可以迅速开发功能和用现有代码开展试验,同时不破坏现有的系统。在项目的整个开发过程中,多数情况下我们都能够实现这个目标。在4个月的开发周期中,虽然游戏玩法几乎仍保持不变,但是我们对其进行了多次较大的改动,而单元测试在这些改动过程中表现出不可估量的价值。

和多数游戏一样,《Battle Group》的开发从游戏设计文件开始。随后,我们制作了游戏原型,修改设计文件,开始构建测试版本。这份设计文件转化成了单元测试。我是项目的程序员,虽然这项工作应该由我来负责,但是我计划将来让游戏设计师来负责编写针对游戏玩法的单元测试。随着项目的进展,游戏的设计结构从静态的设计文件转变成动态的一整套单元测试。随后,团队提出改变设计的需求,我就将时间放在更新单元测试以配合新设计。

Test Driven Game Development(from gamasutra)

Test Driven Game Development(from gamasutra)

构建原型

在项目原型构建阶段(游戏邦注:作者的团队在这个时期大约耗费1周的工作时间),我们并没有创建这些单元测试,因为这是个之前被遗弃的原型,单元测试只会放慢我们的开发速度,而且没有多大的价值。TDGD有个弱点,就是在对代码库进行重大改变时会影响到开发速度。而在原型构建阶段经常需要改动代码库,因而我不推荐在开发原型阶段使用TDGD,尤其是之前被遗弃的原型。

但是,原型阶段是开始充实测试设计的绝妙时机。因为你正在处理的是游戏核心功能,所以你可以看到主要的代码编写精力要放在哪里,而且也能了解到哪些区域在项目的开发过程中最容易发生改变。《Battle Group》中的爆炸半径和武器速度便是绝佳的例证。这些方面的小改动会对游戏的感觉、流程和易用性产生很大的影响。出于这个原因,我采取措施确保这些部分既能够很容易地更改又能够在更改后保持稳定。随着速度增加,每帧的移动距离也会变大。老式移动设备的物理帧率较低,所以对变化的帧率和数据值进行重复性测试就变得至关重要。因为我已经计划在原型构建后执行TDGD,所以我会特别注意这些区域的代码,有意对这些区域进行彻底的测试。

代码覆盖范围

无论何时,当我同游戏开发行业内外的人谈论起TDD时,往往会就代码覆盖方面展开激辩。代码覆盖指单元测试所“覆盖”的产品代码比例。有人认为,如果代码覆盖没有达到100%的话,就不能算是真正使用TDD,也有人认为覆盖比例只要满足需求即可。我的观点是,代码覆盖应当由游戏/代码本身来决定。有时候,某些测试带来的麻烦要多于好处(游戏邦注:比如,修改几行代码需要更新10倍行数的单元测试代码)。对于这种情况,要么重新考虑执行的测试方式,要么完全取消此类测试。

对于《Battle Group》的代码覆盖,我追求的是实际效果。我的主要关注点是,利用我有限的资源获得最好的效果,因为我是团队中唯一的程序员。在原型构建和整个项目开发期间,我注意哪些区域的代码比较关键或经常被破坏,尽量给它们提供最高的代码覆盖范围。

测试优先开发方式

我选择“测试优先”开发方式,创建我的单元测试,然后执行需要测试的功能。这使我可以根据代码应有的用途进行设计,而不是根据问题来编写解决方案。我发现,这可以保持关注点的独立性,确保所有东西都能够尽可能地模块化。优先思考功能性的外部界面,这有助于我进入制作自己所需内容的思想状态。在解决某个有趣和复杂的问题时,我们很容易遗忘原本的代码存在目的。测试优先开发方法可以助你将精力放在最需要的地方,确定随后执行代码片段的功能性。

NCrunch

在《Battle Group》开发期间,NCrunch工具发布。这个工具会完全改变你的单位测试方法。我无法在此阐述更多细节内容,但是我强烈推荐你获取和试用这个工具。整个系统可以概括成两点:

1、单元测试代码含有即时内联红绿灯,显示当前状态能否通过测试。测试保持在后台的运行,持续更新状态。

2、产品代码有个类似的红绿灯系统,显示覆盖这段代码的测试通过和失败情况。

结论

从总体上说,我对TDGD给《Battle Group》开发带来的帮助很满意,我计划在未来项目中继续使用这种方法。尽管在执行某些新功能时会略微减慢,但整体开发速度获得了提升。

游戏邦注:本文发稿于2011年11月24日,所涉时间、事件和数据均以此为准。(本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

Test Driven Game Development Experiences

Alistair Doulin

We’ve just wrapped up our first game that employs full Test Driven Development (TDD) practices. I’ll share my experiences, good and bad, now that we’re completely finished the first version of the project. I’ve spoken previously about Test Driven Game Development (TDGD) but a lot of that was theoretical so today I’d like to give some more concrete thoughts on how TDGD helped with the creation of Battle Group.

Test Driven Game Development?

The idea behind Test Driven Game Development is writing automated Unit Tests to confirm the correctness of your production code. Unit tests can be written to test all areas of a game from gameplay to rendering engines and anything in between. As I stated previously, there are three main goals from TDGD:

Find out when code breaks. If you make a code change that breaks something that was previously working (and is tested) you will know about it immediately.

Forces modular code. For code to be “unit testable” it must be modular with clear boundaries and “separation of concerns” between various systems.

Allows you to “find the fun”. Once you’re guaranteed that certain requirements are met, you’re free to experiment with gameplay to make it more enjoyable without breaking the game. This also extends to giving designers more freedom when scripting. Encouraging experimentation by designers without programmer intervention is invaluable.

How We Did It

In general the development of new gameplay features followed these steps:

Design the unit test for the new feature, testing the outward interface seen by the rest of the system

Implement the feature as quickly as possible, cutting corners as needed so the feature is playable in the quickest time possible

The team tests the feature, makes sure it’s working and fun and we make iterative improvements to it

Once happy with the feature I then refactor the code to clean up any shortcuts and make it the optimal solution

This system worked extremely well and lead to a high development velocity both for the creation of new features and maintenance/update of existing features.

Gameplay Testing

All of the unit tests created for Battle Group were testing gameplay code as this is the most complex area of our game. Unity does the heavy lifting for most of the technology based systems (eg rendering, input) freeing us to have ~90% of the code in the project related directly to gameplay. The main motivation for this testing was to allow us to rapidly develop features and experiment with existing code without breaking existing systems. For the most part, this motivation was met throughout the lifetime of the project. While the gameplay stayed fairly constant throughout the 4 month development cycle, there were a couple of major changes we made and the unit tests were invaluable during these changes.

Battle Group started out (as most games do) as a game design document. We then prototyped the game, tweaked the design document and began working on the alpha build. This design document was translated into unit tests. While I (the programmer) was responsible for this, I plan in the future for our game designer to begin to take over the role of writing gameplay specific unit tests. As the project progresses, the artifacts of its design move from a static design document/wiki to an active, maintained set of unit tests. As design changes requests came in from the team I would focus my time on updating the unit tests to match the new design.

What About Prototyping?

We did not create these unit tests during the prototyping stage (about a week’s work) as this was a throwaway prototype and unit tests would have slowed us down without much real value. One of the negatives related to TDGD is the reduction in velocity while making fairly major changes to the codebase. This is often the case while prototyping and therefore I strongly urge against TDGD during the prototyping phase of your development, particularly throw away prototypes.

The prototyping phase is a great opportunity to start fleshing out the design of your testing suite however. As you work on the core features of the game you can see where the bulk of the coding effort is likely to go and also which areas are most susceptible to change throughout the life of the project. A good example of this was the blast radius and velocity of the weapons used in Battle Group. Small changes to this had a major impact on the feel, flow and accessibility of the game. For this reason I made sure that this was both easy to change and robust in the changes I made. As velocities increased the distance traveled per frame became quite large. Coupled with this was the low physics frame rate on older mobile devices and it was crucial that we had repeatable behavior at varying frame rates and data values. As I had already planned to implement TDGD post-prototype I was mindful of these areas of code and made a mental note to test these areas first and thoroughly.

Code Coverage

Whenever I discuss TDD with someone in or outside the game development industry, there’s often a heated discussion about code coverage. Code coverage is the percentage of production code that is “covered” by unit tests. There are purists that claim you’re not really doing true TDD without 100% code coverage and there are others who say some arbitrary percentage is enough. My stance is that the game/code itself should determine the code coverage you should aim for. Sometimes certain tests are causing more trouble than good (eg changing a few lines of code requires 10 times more lines of unit testing code to be updated). Either the tests need a rethink in the way they are implemented or it’s best just to remove them.

I didn’t “watch the clock” when it came to code coverage on Battle Group. My main focus was to get the best value from my limited resources (as the sole programmer on the team). During prototyping and throughout development I noted which areas of code were critical or breaking often and made sure to get the highest code coverage possible on them. There is certainly a point of diminishing returns when it comes to code coverage which differs between projects and between systems within a project.

Test First Development

I opted for a “Test First” development style where I would create my unit test and then implement the feature being tested. This allowed me to design exactly how I thought the code should be used rather than writing the solution to the problem. I found this was invaluable to keeping a clear separation of concerns and made sure everything was as modular as possible. By thinking about the outward interface of the functionality first, it helped me get in a mindset of creating exactly what I wanted. When solving an interesting and complex problem it’s easy to lose site of the original reason for the code’s existence. Test first development focuses your efforts where they are needed most, on defining and then implementing the functionality of a piece of code as seen by the rest of the system.

NCrunch

One tool that was released during the development of Battle Group was NCrunch. This tool will completely change the way you unit test. I won’t go into too much detail as it’s a little off topic, but I strongly recommend you grab it and experiment with how it works. The whole system can be summed up in two points:

Unit test code has real-time inline green/red lights to indicate whether it is currently passing. Tests are continually running in the background to keep this constantly up to date

Production code has a similar green/red light system showing how many unit tests covering this code pass and fail (or if there are no tests at all)

Conclusion

Overall I was really happy with the way our TDGD turned out on Battle Group and I definitely plan on adopting it again for future projects. My development velocity increased overall while being slightly slower at the point of implementation of a new feature.

Have you used TDD on game projects? Do you have any experiences you can share? Or do you think this is all a load of crap and I should get off my soapbox and get back to coding the game instead of the unit tests? (Source: Gamasutra)


上一篇:

下一篇: