大纲1.什么是测试驱动开发2.测试驱动开发对各个阶段的影响3.测试驱动开发存在的问题4.当前面临的问题5.单元测试6.测试工具7.总结8.相关资料1.什么是测试驱动开发1.概述测试驱开发,简称TDD,是一种不同于传统软件开发流程的新型开发方法。要求在编写某个功能代码之前先编写测试代码,然后只编写测试同过的功能代码,通过测试来推动整个开发的进行。这有助于编写简洁可用和高质量的代码,并加速开发过程。1.什么是测试驱动开发类似于工人师傅砌墙时,会先用桩子拉上线,以便于砖能够垒的笔直,因为垒的时候都是以这根线为基准的。TDD就像这样,先写测试代码,就像工人师傅先用桩子拉上线,然后编码的时候以此为基准,只编写符合这个测试的功能代码1.什么是测试驱动开发2.背景测试驱动开发(TestDrivenDevelopment,英文缩写TDD)是极限编程的一个重要组成部分,它的基本思想就是在开发功能代码之前,先编写测试代码。也就是说在明确要开发某个功能后,首先思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码满足这些测试用例。然后循环进行添加其他功能,直到完成全部功能的开发。代码整洁可用(cleancodethatworks)是测试驱动开发所追求的目标。1.什么是测试驱动开发3.测试驱动开发的优点跟测试后进行的开发方式相比,它有如下好处:(1)测试驱动开发最重要的功能还在于保障代码的正确性,能够迅速发现、定位bug。针对关键代码的测试集,以及不断完善的测试用例,为迅速发现、定位bug提供了条件。(2)通过编写代码的测试用例,对其功能的分解、使用过程、接口都进行了设计。而且这种从使用角度对代码的设计通常更符合后期开发的需求。可测试的要求,对代码的内聚性的提高和复用都非常有益。因此测试驱动开发也是一种代码设计的过程。(3)写单元测试的时候,很容易就可以为一个行为写一个测试用例,让它通过,然后为另一种行为写另一个测试用例。也就是说,整个任务会被划分成很多小的任务,独立完成。如果我们不用TDD而直接实现的话,我们很容易就会同时把所有的行为都实现了。这样花的时间长,而且在这相当长的时间里面,写的代码都是没有测试过,不能保证准确性的。相反的,用TDD的话,我们只实现要测的行为的代码。它只花费很少的时间(几分钟),而且可以马上测试。1.什么是测试驱动开发(4)为了更容易的写单元测试,我们会广泛的使用接口。这个会让单元测试代码很容易读跟写,因为测试代码里面没有多余的数据。如果我们不用TDD而是直接写实现的话,我们经常会使用现成的类,测试为了调用现成的类,就不得不创建很多多余的数据,创建很巨型的对象。(5)因为广泛的使用接口,我们的类之间就不会藕合,因此重用性更好。(6)发现比传统测试方式更多的Bug。(7)完工时完工。表明我可以很清楚的看到自己的这段工作已经结束了,而传统的方式很难知道什么时候编码工作结束了。1.什么是测试驱动开发4.测试驱动开发的过程1)明确当前要完成的功能。可以记录成一个TODO列表。2)快速完成针对此功能的测试用例编写。3)测试代码编译不通过。4)编写对应的功能代码。5)测试通过。6)对代码进行重构,并保证测试通过。7)循环完成所有功能的开发。简单来说,就是不可运行/可运行/重构。1.什么是测试驱动开发5.原则(1)测试隔离。不同代码的测试应该相互隔离。对一块代码的测试只考虑此代码的测试,不要考虑其实现细节(比如它使用了其他类的边界条件)。(2)一顶帽子。开发人员开发过程中要做不同的工作,比如:编写测试代码、开发功能代码、对代码重构等。做不同的事,承担不同的角色。开发人员完成对应的工作时应该保持注意力集中在当前工作上,而不要过多的考虑其他方面的细节,保证头上只有一顶帽子。避免考虑无关细节过多,无谓地增加复杂度。(3)测试列表。需要测试的功能点很多。应该在任何阶段想添加功能需求问题时,把相关功能点加到测试列表中,然后继续手头工作。然后不断的完成对应的测试用例、功能代码、重构。一是避免疏漏,也避免干扰当前进行的工作。(4)测试驱动。这个比较核心。完成某个功能,某个类,首先编写测试代码,考虑其如何使用、如何测试。然后在对其进行设计、编码。(5)先写断言。测试代码编写时,应该首先编写对功能代码的判断用的断言语句,然后编写相应的辅助语句。1.什么是测试驱动开发(6)可测试性。功能代码设计、开发时应该具有较强的可测试性。其实遵循比较好的设计原则的代码都具备较好的测试性。比如比较高的内聚性,尽量依赖于接口等。(7)及时重构。无论是功能代码还是测试代码,对结构不合理,重复的代码等情况,在测试通过后,及时进行重构。(8)小步前进。软件开发是个复杂性非常高的工作,开发过程中要考虑很多东西,包括代码的正确性、可扩展性、性能等等,很多问题都是因为复杂性太大导致的。极限编程提出了一个非常好的思路就是小步前进。把所有的规模大、复杂性高的工作,分解成小的任务来完成。对于一个类来说,一个功能一个功能的完成,如果太困难就再分解。每个功能的完成就走测试代码-功能代码-测试-重构的循环。通过分解降低整个系统开发的复杂性。这样的效果非常明显。几个小的功能代码完成后,大的功能代码几乎是不用调试就可以通过。一个个类方法的实现,很快就看到整个类很快就完成啦。本来感觉很多特性需要增加,很快就会看到没有几个啦。你甚至会为这个速度感到震惊。(个人理解,是大幅度减少调试、出错的时间产生的这种速度感)1.什么是测试驱动开发6.怎么编写测试用例测试用例的编写就用上了传统的测试技术。操作过程尽量模拟正常使用的过程。全面的测试用例应该尽量做到分支覆盖,核心代码尽量做到路径覆盖。测试数据尽量包括:真实数据、边界数据。测试语句和测试数据应该尽量简单,容易理解。为了避免对其他代码过多的依赖,可以实现简单的桩函数或桩类(MockObject)。如果内部状态非常复杂或者应该判断流程而不是状态,可以通过记录日志字符串的方式进行验证。2.测试驱动开发对各个阶段的影响1.传统软件开发模型传统的软件开发包括需求分析、设计、编码和测试四个阶段。2.测试驱动开发对各个阶段的影响2.测试驱动开发对需求分析的影响1)需求分析是软件开发的一个重要任务,它是系统层软件配置与软件设计之间的桥梁,它主要是用来识别软件的功能、性能的要求及其接口特性。2)测试驱动开发在软件需求分析阶段引入测试。需求分析阶段的测试工作的重点是如何定义测试计划,如何定义接受测试并获得客户的认可,在需求分析阶段结束的时候,必须保证所有的需求都是可测试的,都拥有测试用例,需求阶段另一个重要的测试任务是准备构建测试沙盒,建立一个测试环境,以及这个软件项目所需要的测试数据。通过在需求分析阶段引人测试,开发人员能够清除各种矛盾以满足用户需求。2.测试驱动开发对各个阶段的影响3.测试驱动开发对设计的影响1.软件设计分为:概要设计和详细设计。概要设计主要是把需求转换为数据和软件体系结构,而详细设计主要集中在体系结构表达式的细化,从而产生详细的数据结构和软件的算法表达式。2.测试驱动开发在测试阶段引入测试,用测试来推进设计。3.坚持测试优先的实践,使设计人员用测试的角度来考虑设计,从一个外部接口和客户端的角度来考虑问题,这样可以保证软件系统各个模块之间能够较好的连接在一起,而设计人员的思考方式,也会逐步地从单纯的考虑实现,转移到对软件结构的思考上来。2.测试驱动开发对各个阶段的影响4.引用测试驱动开发,因为测试可以实现一部分的设计工作。这样,设计上可以节省一些工作量。5.在设计阶段,测试工作的重点在于如何定义各个模块的详细测试内容,最好的方式是实现测试代码,并构建测试框架,对于一些比较复杂的项目,还需要编写一些测试工具。2.测试驱动开发对各个阶段的影响4.测试驱动开发对编码的影响编码就是用编程语言把对软件的设计表达式翻译为计算机能够“懂得”的形式。测试驱动开发在编码阶段引入的观点是:由测试来决定如何编写代码。这种观点强调在写程序之前先写测试,这会使开发人员更清楚应该在程序中加入何种性能,以及如何使这些性能良好的运行。由于许多需求并不明确或并不完整,开发人员不得不花时间把它弄清楚或是根据以往的实践经验来选择一个较好的办法。如果所有不明确的问题都能在写测试的时候首先被解决,那么就会使之后复杂的编写代码工作变得很轻松。2.测试驱动开发对各个阶段的影响5.测试驱动开发对测试的影响1)软件测试的概念软件测试是软件质量保证的主要活动之一,它提供对软件规格说明、设计和编码的最终评审。软件测试的本质是针对要测试的内容确定一组测试用例。我们可以通过维恩图来理解测试。维恩图中的全域表示的是程序行为,集合P是用程序实现的行为,集合S是所描述的行为,集合T是测试用例。S和P相交的部分是“正确”的部分,即既被描述又被实现的行为。测试就是确定既被描述又被实现的程序行为的范围,因此,我们要使S、P和T的相交区域1尽可能地大。如下图。2.测试驱动开发对各个阶段的影响S•1TP2.测试驱动开发对各个阶段的影响2)件测试的方法软件测试有两种基本的方法:功能性测试和结构性测试。功能性测试把任何程序都看作是从输人定义域取值映射到输出值的函数。它使用的唯一信息是软件的规格说明。结构性测试检查软件内部的逻辑结构,是以仔细检查过程的细节为基础,通过提供一组指定条件和循环的测试用例,对穿过软件的逻辑路径进行测试,可以在不同点检查程序的状态,以确定实际状态与预期状态是否一致。2.测试驱动开发对各个阶段的影响3)传统测试方法存在的问题①传统的功能性测试存在着漏洞和冗余,而且同时不能被发现。②传统的结构性测试将代码采用有向图表示和程序路径公式化,掩盖了代码中的重要信息,这就在路径分析的方向上走得太远。③如果测试编写人员编写测试时所依赖的是文档不是代码时,当文档和代码存在任何不一致的地方就会造成问题。④测试不是自动运行的,它们极有可能不会被频繁、经常性地运行,或每次都以相同的方式来运行。2.测试驱动开发对各个阶段的影响4)测试驱动开发解决的问题①在测试驱动开发中,由程序员来完成测试。代码是基于测试编写的,就保证了代码的可测试性,有助于确保测试覆盖的完备性及代码与测试的一致性。②测试驱动开发把功能性测试和结构性测试覆盖指标相结合,通过这种结合确定测试用例,从而实现功能性测试的置信和结构性测试的度量。③测试驱动的开发在整个开发过程中引入自动测试,并不断改进这些测试以适应程序代码的扩展。④全面彻底的测试覆盖,如果在调试阶段引入某个错误的话,测试集就能立即发现并查明其位置。测试一调试周期就会被控制在相当短的时间内,从发现错误到修正它就不会再耽搁很长的时间。⑤当系统被发布时,详尽的测试集与系统一起发布,从而使得将来对程序的修改和扩展更加容易。3.测试驱动开发存在的问题①测试驱动开发是单元性测试,不是全局性测试。因此在对模型的验证方面起不到关键作用。②如果用测试驱动开发驱动图形用户界面的编写,容易造成资源浪费。③测试驱动开发的应用领域问题。对于设计很重要必须提前做好的软件,对于软件质量要求极高的军事或科研产品如神州六号,对于人命关天的软件如医疗设备软件等等不适合用测试驱动开发。4.当前面临的问题1.测试用例不全面2.测试数据依赖于整个系统环境,不能回归3.对于复杂的函数或接口的测试没有实现4.没有建立测试基础类5.系统更新变化快,用例不能及时更新6.测试函数的功能不唯一5.单元测试1.概述单元测试的目标:确保模块被正确地编码。由谁去做:通常由开发人员执行。怎样去测试:功能测试可以用黑盒测试方法,代码测试可用白盒测试方法。什么时候停止:当开发人员感到代码没有缺陷时。5.单元测试2.单元测试的重要性一个尽责的单元测试方法将会在产品开发的某个阶段发现很多的Bug,并且修改它们的成本也很低。系统开发的后期阶段,Bug的检测和修