http://blog.sina.com.cn/s/blog_626bfd2b01017m70.html
测试驱动开发(TDD)
在敏捷开发中,我们强调通过快速迭代,客户反馈,精简文档等来更加灵活快速地响应变更,不断得修正计划,那在这个过程中如何保证代码的质量呢?
极限编程就是这样一套方法来帮助团队提高软件开发的效率,质量,使得团队在每个迭代真正能完成一些达到“Done,Done,Done,Done”要求的成果。
这里我们先讨论测试驱动开发(TDD),极限编程的方法之一,从业务入手,以测试先行的方法来反向推动代码的实现。那什么是TDD呢?
简单的说,就是每当需要添加一个新功能,或修改现有功能时,我们首先思考这部分代码期望达到的输入与输出,先把验证该业务的单元测试用例写出来,再去写最简单的实现代码来通过该测试;不断重复此过程直到完成整个功能。
注意:TDD是开发人员的责任。
典型的TDD开发步骤:
Step 1:分析并确定一个目标测试场景
Step 2:添加一个单元测试来验证该测试场景的输入输出
Step 3:运行该测试,得到失败的测试结果
Step 4:写最简单的功能代码来通过该测试
Step 5:再次运行该测试,看到测试通过
Step 6: 进行代码重构,包括功能代码和单元测试代码
Step 7:重复以上步骤,直至开发完成
建议在TDD的开发遵循的最佳实践:
1. 简化 -保持测试用例尽可能的简单,以验证业务为首要目标
2. 业务导向 -针对每一个需要验证的业务场景进行测试,而不是对代码的每一个方法进行测试
3. 隔离 -每一个测试类(TestCase)都可以单独运行,不依赖于其它任何测试;同样很多测试类也可以一起执行,而不会互相干扰
4. 重构 -开发出来的单元测试和功能代码都需要不断重构,改进代码的可读性,可维护性,减少冗余代码等
5. 测试列表 -在开始开发之前,先列出所有需要的测试,并在开发中不断维护该列表,避免遗忘一些必要的测试
6. 反向工程 -在某些开发环境中,如Eclipse,可以使用IDE所提供的反向工程能力从测试代码自动生成必要的类,方法等
7. 注释 -不需要另外单独的文档,而是在测试类中对每个测试方法对应的业务场景,输入和期望的输出进行详细的描述
TDD 的好处:
1. 提高代码质量 -测试先行,并达到足够的覆盖率,确保代码都经过了测试
2. 对系统进行描述 - TDD产生的测试就是对系统的一套完整的说明文档,所有的业务,每一个方法的使用在这里都有描述。如果谁想深入了解你的系统,让他去看测试:)
3. 自信得重构 - TDD能够产生一组完备的测试套件,每当我们重构了代码后,可以方便得通过执行已有的测试来确保新的改进没有影响到代码的逻辑和行为;同样,当每次解决了缺陷后,可以通过执行已有的测试来确保其它功能没有受影响,没有引入新的缺陷。这让团队可以更加自信得去进行代码重构
4. 自动回归测试 - TDD产生的测试套件可以与自动化的持续进程环境相结合,部分实现回归测试的自动化,大大提高回归测试的频率,同时减少所花费的时间
5. 消除浪费 - TDD有助于开发团队避免产生冗余的,没有用的代码;避免那些没有任何人能看懂的不知所谓的代码
6. 减少Debugging - 通过TDD来进行编程,可以大大减少对代码 Debugging 的需要,节省时间
7. 简化设计 - 在概要设计的指导下,TDD能够替代大部分甚至全部的详细设计(实现类,方法级别的设计)
8. 更加模块化 - 由于TDD需要开发人员将一个功能分解为一个个可以测试的更小单元,然后再集成为一个整体。这样的思维和开发模式将产生更小的,更清晰的,更加责任明确的类,更加松耦合的组件和清晰的接口
TDD 的局限性:
1. TDD产生的代码质量取决于测试的质量,即不恰当,不正确的测试会产生错误的代码;业务场景覆盖不充分的测试会产生功能不完整的代码;
2. TDD只适用于输入输出明确的开发项目,不适用于某些探索性的,输出不确定的开发,比如密码系统,人工智能,安全等领域的研发;
3. TDD在某些环境下实施会有一些困难,比如关系型数据库,异步通信等,需要一些额外的辅助工具。
最后,作为一种新的设计和编程方法,在项目中实施TDD 对开发人员提出了一些新的挑战:
* 职责转变 - 某些开发人员认为,他的工作就是实现功能,写代码;测试只是测试人员的事情,不在他的职责范围内。这是错误的认识,完备高质量的单元测试也是开发人员的职责!
* 思维转变 - 很多开发人员拿到需求后,喜欢立刻就埋头开始写代码实现。TDD要求测试为先,开发人员首先要思考的不再是功能如何实现,而是应该如何去进行验证,并列出需要的测试场景。这是一个大的逆向思维转变。
* 需求分析能力 - TDD比传统的编程方法需要开发人员具备更强的需求分析能力,要求开发人员对业务有跟深入的理解。
验收测试驱动开发(ATDD)
在上文讨论的 TDD 只是开发人员的职责,通过单元测试用例来驱动功能代码的实现。但如何保证开发人员书写的测试用例确实满足需求,并且覆盖完全的呢?把代码的质量放到一个更大的层面,测试人员应该如何介入敏捷流程来提高测试效率和提高产品质量呢?
我们这里来讨论在测试驱动的方向商另一个人们的话题:验收测试驱动开发(ATDD)。什么是验收测试驱动开发?
在准备实施一个功能或特性之前,首先团队需要定义出期望的质量标准和验收细则,以明确而且达成共识的验收测试计划(包含一系列测试场景)来驱动开发人员的TDD实践和测试人员的测试脚本开发。
注意:测试人员必须是团队的一部分,并在ATDD的过程中扮演关键和掌控性的角色。
典型的ATDD开发过程是:
Step 1:产品负责人向测试人员和开发人员讲解用户故事,澄清他们提出的问题;
Step 2:
a. 测试人员列出验收该功能所需要验证的所有测试场景,每个测试场景通常是概要,清晰的一句话;
b. 同时,开发人员查找和分析现有的相关设计,代码和单元测试,明确开发策略;
Step 3:测试人员和开发人员共同评审和调整测试场景列表,达成共识;必要时寻求产品负责人的参与和确认;并且明确那些场景应该有单元测试;
Step 4:
a. 基于通过评审和认可的测试场景列表,测试人员开始为每个测试场景创建详细的验收测试用例,准备测试脚本和测试数据;
b. 同时,基于同样的的测试场景列表,开发人员开始添加单元测试用例,并以TDD方式驱动业务实现;
Step 5:在开发人员将完成的功能部署并交付测试人员执行测试之前,进行代码评审和根据测试场景列表快速验证自己完成的功能,甚至邀请测试人员来观摩;
Step 6:一旦完成的功能通过构建并部署到测试环境,测试人员立刻开始执行测试脚本;
Step 7:任何测试人员的测试中发现的缺陷都要纪录到工具,并跟踪,立刻反馈给开发人员解决,或进行其它恰当处理;
Step 8:最后,在迭代结束,团队成员向产品负责人和客户演示完成的功能。具体演示那些场景,可以在Step 3 的阶段就确定。
由此可见,ATDD 和基于单元测试的 TDD 一样,也充分体现了敏捷开发“业务驱动”的特点,始终从用户的业务价值出发;对开发团队来说,ATDD 是由外向内,多方介入的,基于拉动策略的,并行开发测试方法;确保所有交付的产品都经过了充分的测试。
ATDD 的好处:
1. 提高交付产品的质量- 因为测试人员早期介入所发挥的驱动作用,确保所有交付的功能都经过了测试,并且提高了测试的覆盖和准确性;
2. 提高TDD质量 -测试人员和开发人员共同定义测试场景列表,并且经过评审和修订,可以帮助开发人员书写高质量的,覆盖完整的单元测试用例。有助于解决上面提到的TDD方法的第一个局限性;
3. 回归测试 -良好定义和覆盖完整的单元测试和验收测试脚本有助于未来进行回归测试;
4. 消除浪费 -以ATDD的方式,满足和通过所有的验收测试场景是开发的首要目标,可以避免团队进行一些对客户没有价值的工作,减少浪费
在ATDD的实施中,最主要的变化和挑战在于测试人员的工作方式和流程,对测试人员提出了这些挑战:
* 角色转变 -传统的很多项目中,测试人员通常都是一个独立的QA团队,与开发团队彼此协作较少,测试人员的工作以开发人员完成开发和部署为起点;在敏捷开发中,尤其是ATDD的团队中,测试人员必然是开发团队的成员,而且是处于支配作用的重要成员。测试人员不能继续是被动的,而是以自己的成果来带动开发实现;
* 沟通能力 -传统项目的测试通常基于完备详细的需求文档;然而在敏捷开发中,需求都是以用户故事的简化形式定义的,测试人员要列出准确完备的测试场景,他需要更多地和产品负责人,开发人员沟通,每天紧密工作在一起,有任何问题或变化都能立刻反馈到团队其他成员;
* 探索性测试 -由于敏捷用户故事地简单性,而ATDD基于预定义的测试场景列表开发测试脚本,并且部分测试场景有了自动化的单元测试,不再需要重复的手动测试脚本,因此 ATDD 中的测试脚本不一定能覆盖到所有的逻辑和质量标准,于是“探索性测试”成为一个新的热门话题。即在既定的测试脚本以外,对当前和相关功能进行探索性的测试,尝试去发现一些隐藏的,未知的错误,或者一些不完美的地方。这应该成为测试人员的一种工作习惯。探索性测试看似随意,也有一些可遵循的方法,比如卖点测试、破坏测试、极限测试、深巷测试等。