游戏开发流程有没有是测试驱动开发实用指南的

推送到 Kindle 的服务已停止,如需 mobi 版,请看
敏捷大师Bob大叔作序推荐//提供大量现代C++编程的实践和技巧//深入探讨测试驱动开发、设计原则和优秀软件开发工艺
第 3 章 测试驱动开发基础
第 4 章 测试结构
第 5 章 测试替身
第 6 章 增量设计
第 7 章 高质量测试
第 8 章 遗留代码的挑战
第 9 章 测试驱动开发与多线程
第 10 章 测试驱动开发的其他概念和讨论
第 11 章 发展和维持测试驱动开发
附录 A 比较单元测试工具
附录 B 代码Kata:罗马数字转换器
附录 C 参考文献
原书出版社
他们拥有这本书我的神呀,测试驱动开发真的有效!
发表于 09:15|
摘要:测试驱动开发(TDD)和行为驱动开发(BDD)是如何影响你们的代码质量的?这篇文章里,作者将通过分析当前工作中的真实数据来回答这个问题。
我们经常听到人们宣扬说,在开发软件时写测试代码(单元测试,功能测试等)能有效的减少产品中的bug。如何验证这样的言论?通常,这些人都是已经在使用驱动测试开发(TDD)或行为驱动开发(BDD),而且,他们所在的公司在诞生第一天起就有着很强的测试文化。然而,如何能测量不写测试程序造成的影响?如何能验证实践TDD能真正的减少bug的存在?我们能否在一段时间里停止写测试程序,看看这对软件缺陷数有多大的影响?这方法看起来不太现实。
这篇文章里,我将通过分析当前我工作中的真实数据来回答这个问题。以前我们的系统没有测试代码,可一旦开始进行测试驱动开发,我就成为了这种开发方法的强力倡导者。
测试驱动开发前的背景情况
我做Web开发已经有10年了,从2009年起就开始耳闻驱动测试开发(TDD)。从那时开始我就打算要多学学这方面的知识。我在当时的公司里已经干了两年,现在已经是2012年,在这个公司总共工作了5年,从后3年开始实施测试驱动开发方法。
这个过程非常有趣,因为如今我可以浏览我们的bug跟踪系统,汇总这段时间的缺陷统计数据,看看TDD对我们的程序代码质量有多大的影响。
简单的说一下我们的软件技术构成:我开发基于PHP和Javascript的系统UI部分。我是UI部分的主要开发人员,这就是说,如果UI上有bug,我基本上要对此负责。UI跟后台的C++服务交互,这服务运行在Oracle数据库上,通过PLSQL代码处理数据。
UI部分的PHP代码是唯一实施了测试驱动开发的地方。我们的Javascript代码没有测试程序。遗憾的是,公司里的程序员都没有测试驱动开发的实践经验。系统其它层面的代码都没有实施单元测试或功能性测试(公司有专门的QA团队在开发完成之后进行测试)。
这使得我们的统计分析数据看起来非常的明显。我可以看到,随着时间的推移,我们的整个产品和UI模块的缺陷数字的变化。这使得我有办法来回答最初的问题,这个问题在此可以用这样的问题复述:使用测试驱动开发能使我负责的UI模块的缺陷数下降吗?
测试驱动开发真的有效吗?
下面这个图表是由我们的bug跟踪系统生成的。它向我们展示了UI部分的bug和整个产品的bug的对比比率。
我们可以从中看出一些有趣的事情:
首先,我可以回答这个问题:在UI中发现的bug的数量(下面绿颜色的)减少了50%(起初UI部分相关的bug数量占整个产品的35%,如今它已经降到了15%)。
在历史遗留的程序库上实施测试驱动开发并不会很快的显现出效果。我于2009年第三季度开始TDD。你可以看到,到了2010年第三季度时bug数才开始下降,而到了2011年第三季度开始大幅度下降。将近用来2年的时间实现了50%的降低。
图表上显示,在2010年第二季度和2011年第二季度时,数据上下跳动。看一下我们产品的发布历程,这应该归结于这段时间系统里增加了严重依赖于Javascript的第三方地图API功能。我之前也说过,我们并没有写Javascript上的测试代码。这也证实了不写测试代码会导致更多的bug。
下一步的计划?
之前我说过,我只是在PHP代码上实施了测试驱动开发。观察如今产生的bug,我注意到它们通常可以归为这样两类:
Javascript代码中的bug(这些是没有自动化测试程序的)
系统集成时产生的bug(例如,独立的PHP组件通过了单元测试,但组合起来运行时却出了问题)
这很自然的将我引入了下一步的测试探险:我要在Javascript代码里加入测试套件代码,并最终加入集成测试套件代码。
今年整个夏天我都在做这项工作。对于Javascript代码,开发了基于框架的测试套件。对于端对端的测试,我使用。在这两种情况中,我使用在真实的浏览器中运行测试代码,或胡乱的使用强大的。
我们即将开始下一个开发周期,我期望通过这些新的措施,我们能开发出更有质量的软件。我希望明年还能写出这样的一篇文章来分析这些测试开发工具的效果。很有可能它们会进一步的降低我们的bug数量。
还有,把这些数据分享给我的同事,这让一些开发人员也开始相信测试驱动开发的好处。我们产品中另外一些服务上的开发人员对TDD表现出了兴趣,准备研究如何在他们的程序库上实施测试驱动开发。观察他们的在实践中能获得什么样的成果将会是一件有趣的事情。
你呢?测试驱动开发(TDD)和行为驱动开发(BDD)是如何影响你们的代码质量的?你是否也有类似的统计数据可以分享?请在下面的评论里写出来。
推荐阅读相关主题:
网友评论有(0)
CSDN官方微信
扫描二维码,向CSDN吐槽
微信号:CSDNnews
相关热门文章flash小游戏的测试驱动开发实践(一) - 小明的明天 - 博客园
很久以前就了解到了Test Driven Development。当时的情况是随着游戏的开发,代码通常会越来越多,也越来越混乱。通常书上这时会告诉我们,要重构。可是真的操作起来,还是无从下手。因为重构意味的打破原有的代码结构,而你不知道这样的改变会带来什么样的后果。这时,书上告诉我们,要有测试驱动开发。因为完善的测试能够在我们重构代码后,确认软件的行为没有改变(如果通过了测试)。
但问题是,TDD真的适合游戏开发吗?首先需求的不断改变是游戏开发中的常态,每当需求改变时也就意味这这些测试用例也要发生改变,这会带来许多额外的工作。其次,GUI的自动化测试本身也是一个难题,而游戏又是一个需要大量人机交互的软件。于是我放下了想要实践TDD的念头。
直到有天听了在上的演讲,记住了两句话。第一句话的意思是说,先写测试用例并不是为了今后的测试,而是为了能写出好的代码。因为要想写出测试用例,就强迫你在写具体的代码之前就想好这个模块的接口,即行为。而要想写出简单的测试用例,就意味着模块有较少的依赖。这样的思路实际改变了我在上面对于测试的看法,测试不是为了保障重构,而是希望能在一开始就有好的设计。第二句话是&不可编译-可编译-不可运行-可运行&,这是对具体做法的描述。在第一步写好测试用例时,实际上相关的类里还没有提供接口,因此是不可编译的。从不可编译到可编译是接口的编码。加入接口后,测试可以编译,但是所需的功能还没有实现。而从不可运行到运行则是对接口的具体实现。
我之前编写flash小游戏时习惯采用flash IDE + flashdevelop,使用文档类(document class)结合fla文件中的图像和as文件中的代码。这样的问题在于一个fla文件只能指定一个文档类,不方便编译运行测试。于是根据网上的一些指点,我尝试了swc+flashdevelop的方式,在FD中建立项目时不再采用Flash IDE Project,而是用AS3 Project。然后将fla中的图像资源编译为swc文件,在FD中导入使用。(这些fla中依然可以指定文档类)虽然建立的AS3 Project依然只能指定一个as文件设定&always compile&作为程序入口,但通过Tools-&Flash Tools-&Build Current File (Ctrl+F8)可以运行将任何一个as文件编译为单独的swf,这样便可以单独运行测试用例了。
Blog Stats
Trackbacks - 0  TDD是一个迭代的开发过程,他包括下面的步骤:1.编写测试;2.运行测试,观察失败;3.确保测试通过;4.重构,减少重复。
  每次迭代中,测试就是规范。在我们完成开发之后,测试就可以通过了。之后我们就需要进行减少重复代码和提高设计的重构工作,然后再次运行测试,并保证其通过。
  虽然TDD不主张预期的大设计,但是我们在TDD开始之前还是需要做个简单的设计。我们要如何写自己的第一个设计呢?当我们获得了足够信息可以制定测试的时候,测试代码的编写,本身就是设计。我们指定在特定情况下特定代码的行为,系统之间组件如何响应,以及他们之间如何组合。下面我们会举例,以便大家更好的学习。
  TDD中的迭代时间很短,我们需要非常清楚我们所处的阶段。无论何时我们对代码进行了修改,或者移出了某些功能,我们需要把他们记录在todo列表中,加以观察。这个列表可以是一张纸,或者记事本之类的东西,只要方便你快速的查找和记录既可。在处理这些新的修改点之前,我们需要首先结束本次迭代。本次迭代结束之后,我们再从todo列表中取出一个任务,开始下一次迭代工作。
  步骤1:编写测试
  每次TDD迭代,首先要做的事情是选择一个你要做的功能,为它编写单元测试。单元测试需要简短,测试聚焦在function上的某一个功能点上。比较好的编写测试的规则是,编写尽量少的测试代码就能让测试失败。当然,测试断言不能和之前的测试重复。如果一个测试涉及到系统的两个或两个以上的方面,就说明要么是这个测试太大了,要么是里面包含了重复的测试点。
  测试需要能够描述我们实现的功能,我们的功能代码没做修改,就不需要修改测试代码。
  假设我们要完成一个String.prototype.trim函数的开发,用以去除字符串前后空格。对该方法好的测试,第一步应该是测试前空格是否删除了。
testCase("String trim test", {
  "test trim should remove leading white-space":
  function () {
    assert("should remove leading white-space", "a string" === " a string".trim());
  严谨起见,我们需要先判断字符串包含trim方法。这是因为我们添加的全局函数可能会和第三方代码发生冲突,在代码之前添加 typeof "".trim == "function",可以帮助我们在运行测试之前发现问题。
  为单元测试提供输入条件,运行之后他会判断输出条件是否和预期一致。输入条件不仅仅是函数的参数,任何函数的依赖项,例如全局作用域、某些对象的特殊状态,这些都是输入条件。与此类似,输出结果包括返回值、全局作用域或者相关对象。我们通常把输入输出项分为直接和间接两种。直接项:函数的参数和返回值;间接项:不是以参数形式传入的参数和被修改的外部对象。
  步骤2:观察失败的测试
  测试准备好之后就可以运行了。有很多原因促使我们在编写实现代码之前运行测试,最主要的一点是我们可以用它来确定代码的状态。在写测试的时候,我们会有一个比较明确的期望,测试如何会失败。单元测试虽然不存在逻辑的分支,代码也比较简单,但他也像其他代码一样存在bug。但是运行它,比较期望结果,我们会很快发现这些bug。
  理想情况下,当我们添加了新的测试的时候,我们应该可以运行所有测试用例。这样我们就可以很容易的抓取到干扰测试,例如一个测试依赖于另外一个测试。
  编写实现代码之前运行测试,可以告诉我们一些新的事情。有时候会有这样的经历,在我们写任何实现代码之前测试可以正常通过。一般情况下,这样的事情不应该发生,TDD教导我们编写不能通过的测试。因为我们是先写测试代码,功能代码还没有开发,这时测试代码能跑通就说明存在问题。我们需要确定问题的来源,是不是运行环境已经提供了该方法的实现,或者我们有没有必要保留这条测试用例。
  步骤3:确保测试通过
  准备好运行失败的测试代码之后,我们要做的就是编写实现代码,并保证测试代码可以运行通过。有时候我们甚至需要硬编码,不必担心在这个步骤中我们的代码是多么糟糕,在后面的重构环节我们可以优化他。编写实现代码的时候,我们要寻找最明显的简洁实现方式,如果没有我们可以伪造他,而把具体的实现拖延到后面。
  1.你不需要他
  在极限编程中,TDD的精髓是&你不需要他&,意思是直到需要的时候才需要添加相关功能。基于假设添加一些以后可能会用到的代码,会让我们的代码变得很膨胀。对于动态语言,特别是javascript,违反这一原则有时候对我们是有诱惑的,他可以增加代码的灵活性。一个例子是,为函数添加过多的参数。除非有那样的需求,否则不要这样做。
  2.通过String.prototype.trim测试
  下面的代码是为满足之前的测试开发的。
String.prototype.trim = function () {
  return this.replace(/^\s+/, "");
  可以看到,这个实现还不完善,只去除了左空格。但是TDD就是这样的,每一步都很小,只要能让测试通过就可。发现新的需求点后,编写测试代码,然后完善实现代码并通过测试。
  3.能够工作的最简单的方案
  最简单的解决方案有时候意味着,可能要往产品中添加硬编码的代码。因为有时候一般的解决方案可能不是那么明显,我们可以用硬编码的方式推进我们的项目,等到有了解决方案的时候再替换。虽然硬编码可以推进我们的项目,代码质量是我们的最终目标。
  步骤4:移除重复的重构
  最后,最重要也是最有趣的工作就是使代码变得整洁。当实现代码开发完毕,测试顺利跑通之后,我们就可以考虑重构的工作了,把一些重复的代码移除。这期间只有一条准则:测试必须能跑通。关于重构好的建议是,每次只对一个操作进行修改,并保证测试能够通过。重构是对已有代码的维护修改,所以测试不能失败。
  重复的代码可能会出现在不同位置,有时候他是为了解决硬编码的解决方案。如果我们有一个硬编码的假冒响应,我们需要给他添加另外的测试,让他在不同输入的条件下失败。或许我们一时还想不到替换硬编码的方案,但至少我们知道问题的存在,他为我们提供了足够的信息,方便我们找到最终解决方案。
  重复的代码同样可能存在于测试代码中,例如setup中的请求对象和假冒的依赖。测试代码也是代码,同样需要维护,移除重复内容。如果测试代码和系统过于耦合,我们需要抽取帮助方法和对结构进行重构。setup和teardown可以用来集中设置对象的创建和销毁。
  我们在重构的过程中不能让测试失败。如果在重构的过程中我们没有用更少的代码完成工作,我们就需要考虑把工作托到以后再做。
  步骤5:重复工作
  一旦所有工作都完成了,没有重复代码了,也没有重构工作需要做了,这时候就从todo列表中找一个新任务,重复上面的步骤。根据需要重复这样的工作。你熟悉了这一过程之后,可以放大脚步,但是确保你的周期很短,这样可以得到及时的反馈。
  功能满足需求之后,我们可以考虑提高测试的覆盖率,可以添加对边界值的测试、对依赖项的测试、不同输入类型的测试、不同组件之间的整合测试等。下面是我们为String.prototype.trim添加的第二条测试:
"test trim should remove trailing white-space":
function () {
  assert("should remove trailing white-space", "a string" === "a string ".trim());
阅读(...) 评论()}

我要回帖

更多关于 测试驱动的ios开发 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信