(转)浅谈测试驱动开发(TDD)

 

原文地址:http://www.ibm.com/developerworks/cn/linux/l-tdd/html

背景linux

一个高效的软件开发过程对软件开发人员来讲是相当重要的,决定着开发是痛苦的挣扎,仍是不断进步的喜悦。国人对软件蓝领的不屑,对繁琐冗长的传统开发过程的不耐,使大多数开发人员无所适从。最近兴起的一些软件开发过程相关的技术,提供一些比较高效、实用的软件过程开发方法。其中比较基础、关键的一个技术就是测试驱动开发(Test-Driven Development)。虽然TDD光大于极限编程,但测试驱动开发彻底能够单独应用。下面就从开发人员使用的角度进行介绍,使开发人员用最少的代价尽快理解、掌握、应用这种技术。下面分优点,原理,过程,原则,测试技术,Tips等方面进行讨论。web

 

1. 优点框架

TDD的基本思路就是经过测试来推进整个开发的进行。而测试驱动开发技术并不仅是单纯的测试工做。函数

需求向来就是软件开发过程当中感受最很差明确描述、易变的东西。这里说的需求不仅是指用户的需求,还包括对代码的使用需求。不少开发人员最惧怕的就是后期还要修改某个类或者函数的接口进行修改或者扩展,为何会发生这样的事情就是由于这部分代码的使用需求没有很好的描述。测试驱动开发就是经过编写测试用例,先考虑代码的使用需求(包括功能、过程、接口等),并且这个描述是无二义的,可执行验证的。性能

经过编写这部分代码的测试用例,对其功能的分解、使用过程、接口都进行了设计。并且这种从使用角度对代码的设计一般更符合后期开发的需求。可测试的要求,对代码的内聚性的提升和复用都很是有益。所以测试驱动开发也是一种代码设计的过程。测试

开发人员一般对编写文档很是厌烦,但要使用、理解别人的代码时一般又但愿能有文档进行指导。而测试驱动开发过程当中产生的测试用例代码就是对代码的最好的解释。编码

快乐工做的基础就是对本身有信心,对本身的工做成果有信心。当前不少开发人员却常常在担忧:“代码是否正确?”“辛苦编写的代码还有没有严重bug?”“修改的新代码对其余部分有没有影响?”。这种担忧甚至致使某些代码应该修改却不敢修改的地步。测试驱动开发提供的测试集就能够做为你信心的来源。url

固然测试驱动开发最重要的功能还在于保障代码的正确性,可以迅速发现、定位bug。而迅速发现、定位bug是不少开发人员的梦想。针对关键代码的测试集,以及不断完善的测试用例,为迅速发现、定位bug提供了条件。

个人一段功能很是复杂的代码使用TDD开发完成,真实环境应用中只发现几个bug,并且很快被定位解决。您在应用后,也必定会为那种自信的开发过程,功能不断增长、完善的感受,迅速发现、定位bug的能力所感染,喜欢这个技术的。

那么是什么样的原理、方法提供上面说的这些好处哪?下面咱们就看看TDD的原理。

 

2. 原理

测试驱动开发的基本思想就是在开发功能代码以前,先编写测试代码。也就是说在明确要开发某个功能后,首先思考如何对这个功能进行测试,并完成测试代码的编写,而后编写相关的代码知足这些测试用例。而后循环进行添加其余功能,直到彻底部功能的开发。

咱们这里把这个技术的应用领域从代码编写扩展到整个开发过程。应该对整个开发过程的各个阶段进行测试驱动,首先思考如何对这个阶段进行测试、验证、考核,并编写相关的测试文档,而后开始下一步工做,最后再验证相关的工做。下图是一个比较流行的测试模型:V测试模型。


【图 V测试模型】
【图 V测试模型】 

在开发的各个阶段,包括需求分析、概要设计、详细设计、编码过程当中都应该考虑相对应的测试工做,完成相关的测试用例的设计、测试方案、测试计划的编写。这里提到的开发阶段只是举例,根据实际的开发活动进行调整。相关的测试文档也不必定是很是详细复杂的文档,或者什么形式,但应该养成测试驱动的习惯。

关于测试模型,还有X测试模型。这个测试模型,我认为,是对详细阶段和编码阶段进行建模,应该说更详细的描述了详细设计和编码阶段的开发行为。及针对某个功能进行对应的测试驱动开发。


【图 X测试模型】
【图 X测试模型】 

基本原理应该说很是简单,那么如何进行实际操做哪,下面对开发过程进行详细的介绍。

 

3. 过程

软件开发其余阶段的测试驱动开发,根据测试驱动开发的思想完成对应的测试文档便可。下面针对详细设计和编码阶段进行介绍。

测试驱动开发的基本过程以下:

1) 明确当前要完成的功能。能够记录成一个 TODO 列表。

2) 快速完成针对此功能的测试用例编写。

3) 测试代码编译不经过。

4) 编写对应的功能代码。

5) 测试经过。

6) 对代码进行重构,并保证测试经过。

7) 循环完成全部功能的开发。

为了保证整个测试过程比较快捷、方便,一般可使用测试框架组织全部的测试用例。一个免费的、优秀的测试框架是 Xunit 系列,几乎全部的语言都有对应的测试框架。我曾经写过一篇文章介绍CppUnit的文章( http://www.ibm.com/developerworks/cn/linux/l-cppunit/index.html)。

开发过程当中,一般把测试代码和功能代码分开存放,这里提供一个简单的测试框架使用例子,您能够经过它了解测试框架的使用。下面是文件列表。

	project/				项目主目录
	project/test			测试项目主目录
	project/test/testSeq.cpp		测试seq_t 的测试文件,对其余功能文件的测试文件复制后修改便可
	project/test/testSeq.h
	project/test/Makefile			测试项目的 Makefile 
	project/test/main.cpp			测试项目的主文件,不须要修改
	project/main.cpp		           项目的主文件
	project/seq_t.h			功能代码,被测试文件
	project/Makefile		           项目的 Makefile

主要流程基本如此,但要让你的代码很容易的进行测试,全面又不繁琐的进行测试,仍是有不少测试原则和技术须要考虑。

 

4. 原则

测试隔离。不一样代码的测试应该相互隔离。对一块代码的测试只考虑此代码的测试,不要考虑其实现细节(好比它使用了其余类的边界条件)。

一顶帽子。开发人员开发过程当中要作不一样的工做,好比:编写测试代码、开发功能代码、对代码重构等。作不一样的事,承担不一样的角色。开发人员完成对应的工做时应该保持注意力集中在当前工做上,而不要过多的考虑其余方面的细节,保证头上只有一顶帽子。避免考虑无关细节过多,无谓地增长复杂度。

测试列表。须要测试的功能点不少。应该在任何阶段想添加功能需求问题时,把相关功能点加到测试列表中,而后继续手头工做。而后不断的完成对应的测试用例、功能代码、重构。一是避免疏漏,也避免干扰当前进行的工做。

测试驱动。这个比较核心。完成某个功能,某个类,首先编写测试代码,考虑其如何使用、如何测试。而后在对其进行设计、编码。

先写断言。测试代码编写时,应该首先编写对功能代码的判断用的断言语句,而后编写相应的辅助语句。

可测试性。功能代码设计、开发时应该具备较强的可测试性。其实遵循比较好的设计原则的代码都具有较好的测试性。好比比较高的内聚性,尽可能依赖于接口等。

及时重构。不管是功能代码仍是测试代码,对结构不合理,重复的代码等状况,在测试经过后,及时进行重构。关于重构,我会另撰文详细分析。

小步前进。软件开发是个复杂性很是高的工做,开发过程当中要考虑不少东西,包括代码的正确性、可扩展性、性能等等,不少问题都是由于复杂性太大致使的。极限编程提出了一个很是好的思路就是小步前进。把全部的规模大、复杂性高的工做,分解成小的任务来完成。对于一个类来讲,一个功能一个功能的完成,若是太困难就再分解。每一个功能的完成就走测试代码-功能代码-测试-重构的循环。经过分解下降整个系统开发的复杂性。这样的效果很是明显。几个小的功能代码完成后,大的功能代码几乎是不用调试就能够经过。一个个类方法的实现,很快就看到整个类很快就完成啦。原本感受不少特性须要增长,很快就会看到没有几个啦。你甚至会为这个速度感到震惊。(我理解,是大幅度减小调试、出错的时间产生的这种速度感)

 

5. 测试技术

5.1. 测试范围、粒度

对哪些功能进行测试?会不会太繁琐?何时能够中止测试?这些问题比较常见。按大师 Kent Benk 的话,对那些你认为应该测试的代码进行测试。就是说,要相信本身的感受,本身的经验。那些重要的功能、核心的代码就应该重点测试。感到疲劳就应该停下来休息一下。感受没有必要更详细的测试,就中止本轮测试。

测试驱动开发强调测试并不该该是负担,而应该是帮助咱们减轻工做量的方法。而对于什么时候中止编写测试用例,也是应该根据你的经验,功能复杂、核心功能的代码就应该编写更全面、细致的测试用例,不然测试流程便可。

测试范围没有静态的标准,同时也应该能够随着时间改变。对于开始没有编写足够的测试的功能代码,随着bug的出现,根据bug补齐相关的测试用例便可。

小步前进的原则,要求咱们对大的功能块测试时,应该先分拆成更小的功能块进行测试,好比一个类A使用了类B、C,就应该编写到A使用B、C功能的测试代码前,完成对B、C的测试和开发。那么是否是每一个小类或者小函数都应该测试哪?我认为没有必要。你应该运用你的经验,对那些可能出问题的地方重点测试,感受不可能出问题的地方就等它真正出问题的时候再补测试吧。

5.2. 怎么编写测试用例

测试用例的编写就用上了传统的测试技术。

  • 操做过程尽可能模拟正常使用的过程。
  • 全面的测试用例应该尽可能作到分支覆盖,核心代码尽可能作到路径覆盖。
  • 测试数据尽可能包括:真实数据、边界数据。
  • 测试语句和测试数据应该尽可能简单,容易理解。
  • 为了不对其余代码过多的依赖,能够实现简单的桩函数或桩类(Mock Object)。
  • 若是内部状态很是复杂或者应该判断流程而不是状态,能够经过记录日志字符串的方式进行验证。
 

6. Tips

不少朋友有疑问,“测试代码的正确性如何保障?是写测试代码仍是写测试文档?”这样是否是会陷入“鸡生蛋,蛋生鸡”的循环。实际上是不会的。一般测试代码一般是很是简单的,一般围绕着某个状况的正确性判断的几个语句,若是太复杂,就应该继续分解啦。而传统的开发过程一般强调测试文档。但随着开发节奏的加快,用户需求的不断变化,维护高层(需求、概要设计)的测试文档能够,更低层的测试文档的成本的确太大了。并且可实时验证功能正确性的测试代码就是对代码最好的文档。

软件开发过程当中,除了遵照上面提到的测试驱动开发的几个原则外,一个须要注意的问题就是,谨防过分设计。编写功能代码时应该关注于完成当前功能点,经过测试,使用最简单、直接的方式来编码。过多的考虑后期的扩展,其余功能的添加,无疑增长了过多的复杂性,容易产生问题。应该等到要添加这些特性时在进行详细的测试驱动开发。到时候,有整套测试用例作基础,经过不断重构很容易添加相关特性。