测试驱动javascript开发 -- 4.测试驱动开发过程(下)

简介:   TDD是一个迭代的开发过程,他包括下面的步骤:1.编写测试;2.运行测试,观察失败;3.确保测试通过;4.重构,减少重复。   每次迭代中,测试就是规范。在我们完成开发之后,测试就可以通过了。之后我们就需要进行减少重复代码和提高设计的重构工作,然后再次运行测试,并保证其通过。

  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());
}
目录
相关文章
|
19天前
|
Web App开发 JavaScript 前端开发
Node.js开发
Node.js开发
33 13
|
26天前
|
存储 JavaScript 前端开发
深入浅出Node.js后端开发
在数字化时代的浪潮中,后端开发作为连接用户与数据的桥梁,扮演着至关重要的角色。本文将以Node.js为例,深入探讨其背后的哲学思想、核心特性以及在实际项目中的应用,旨在为读者揭示Node.js如何优雅地处理高并发请求,并通过实践案例加深理解。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的视角和思考。
|
23天前
|
Web App开发 开发框架 JavaScript
深入浅出Node.js后端开发
本文将带你领略Node.js的魅力,从基础概念到实践应用,一步步深入理解并掌握Node.js在后端开发中的运用。我们将通过实例学习如何搭建一个基本的Web服务,探讨Node.js的事件驱动和非阻塞I/O模型,以及如何利用其强大的生态系统进行高效的后端开发。无论你是前端开发者还是后端新手,这篇文章都会为你打开一扇通往全栈开发的大门。
|
25天前
|
机器学习/深度学习 人工智能 自然语言处理
智能化软件测试:AI驱动的自动化测试策略与实践####
本文深入探讨了人工智能(AI)在软件测试领域的创新应用,通过分析AI技术如何优化测试流程、提升测试效率及质量,阐述了智能化软件测试的核心价值。文章首先概述了传统软件测试面临的挑战,随后详细介绍了AI驱动的自动化测试工具与框架,包括自然语言处理(NLP)、机器学习(ML)算法在缺陷预测、测试用例生成及自动化回归测试中的应用实例。最后,文章展望了智能化软件测试的未来发展趋势,强调了持续学习与适应能力对于保持测试策略有效性的重要性。 ####
|
26天前
|
Web App开发 开发框架 JavaScript
深入浅出Node.js后端开发
在这篇文章中,我们将一起探索Node.js的奇妙世界。无论你是刚接触后端开发的新手,还是希望深化理解的老手,这篇文章都适合你。我们将从基础概念开始,逐步深入到实际应用,最后通过一个代码示例来巩固所学知识。让我们一起开启这段旅程吧!
|
22天前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端开发
本文将带领读者从零基础开始,一步步深入到Node.js后端开发的精髓。我们将通过通俗易懂的语言和实际代码示例,探索Node.js的强大功能及其在现代Web开发中的应用。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的见解和技巧,让你的后端开发技能更上一层楼。
|
25天前
|
JavaScript 前端开发 API
深入理解Node.js事件循环及其在后端开发中的应用
本文旨在揭示Node.js的核心特性之一——事件循环,并探讨其对后端开发实践的深远影响。通过剖析事件循环的工作原理和关键组件,我们不仅能够更好地理解Node.js的非阻塞I/O模型,还能学会如何优化我们的后端应用以提高性能和响应能力。文章将结合实例分析事件循环在处理大量并发请求时的优势,以及如何避免常见的编程陷阱,从而为读者提供从理论到实践的全面指导。
|
22天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
54 1
|
24天前
|
存储 算法 C语言
用C语言开发游戏的实践过程,包括选择游戏类型、设计游戏框架、实现图形界面、游戏逻辑、调整游戏难度、添加音效音乐、性能优化、测试调试等内容
本文探讨了用C语言开发游戏的实践过程,包括选择游戏类型、设计游戏框架、实现图形界面、游戏逻辑、调整游戏难度、添加音效音乐、性能优化、测试调试等内容,旨在为开发者提供全面的指导和灵感。
40 2
|
26天前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端开发
本文将带你走进Node.js的世界,从基础到进阶,逐步解析Node.js在后端开发中的应用。我们将通过实例来理解Node.js的异步特性、事件驱动模型以及如何利用它处理高并发请求。此外,文章还会介绍如何搭建一个基本的Node.js服务器,并探讨如何利用现代前端框架与Node.js进行交互,实现全栈式开发。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的视角和深入的理解。
19 4