技术开发 频道

软件质量之路(3):测试驱动开发

【IT168 技术文章】

    测试不能够证明错误不存在,只能够证明错误存在。尽可能测试一切可以测试的东西。

    测试是如何驱动开发过程的 

    测试驱动开发起源于XP法中提倡的测试优先实践。测试优先实践重视单元测试,强调程序员除了编写代码,还应该编写单元测试代码。在开发的顺序上,它改变了以往先编写代码,再编写测试的过程,而采用先编写测试,再编写代码来满足测试的方法。这种方法在实际中能够起到非常好的效果,使得测试工作不仅仅是单纯的测试,而成为设计的一部分。为什么这么说呢?

    在编写程序之前,每个人都会先进行设计的工作。可能有些人的设计比较正式,绘制模型,编写文档。有些人的设计只是存在于脑海之中。且不论是设计是精细还是粗糙,你都为随后的编码活动制定了一个标准。这个标准的明确程度和你的设计的细致程度有关。但应该承认,这个标准是不够细化的。因为你的设计不可能精细到代码级的程度。而标准不够明确的则会产生一些问题,例如,在编写代码的过程中,你还可能会发现原先的设计出现问题,从而中途改变代码的编写思路。这将会导致成果难以检验,进度难以度量。

    既然以设计为导向的标准不够明确,不够具体。那什么样的标准才是合适的呢?只能是代码。因为代码是最明确、最具体的。所以测试优先的本质其实是目标管理。编写测试代码其实是在制定一个小目标。这个小目标非常的明确,它规定了你需要设计的类、方法。以及方法需要满足的结果。这些目标制定完成之后,你才开始编写代码来达成该目标。测试的目标要比设计的目标粒度更小,但是成本上却更为经济。其原因有四:

    *细粒度的设计需要花费大量的成本,虽然CASE工具都提供了代码自动生成的功能,但结果往往难以令人满意。所以,设计如果要做到和测试相同的粒度,成本不菲,如果粒度不够细,指导的意义又不够。
    *减轻了测试的工作量。无论是否进行设计工作,测试工作都是不可避免的,先进行单元测试,可以减少后续的测试工作量。
    *采用测试优先的过程中,设计的粒度较大。因为测试可以实现一部分的设计工作。这样,设计上可以节省一些工作量。例如,你不再需要将类图细化到每个方法。
    *在编写测试代码上花费的成本,会在回归测试上得到回报。自动化测试的最大好处就是避免代码出现回归。两相权衡,编写测试的代价其实不高。

    你也许会说,我既不进行如此精细的设计,也不事先编写测试代码,这样的成本不是最低吗?请注意,我们的前提是在讨论高质量的软件设计。在一些规模较小或是开发人员能力极强的项目中,确实可以如此办理。但是对于强调质量的大项目,这种处于混沌状态的开发思路是不可取的。

    测试优先是软件开发中一种细粒度的目标管理方法,通过明确的目标,推动软件开发的进行。

    在业界中,采用测试作为评价软件标准的做法是非常常见的。例如,sun公司就专门设计了测试软件,对各个实现J2EE规范的产品进行测试。使用测试作为规范的最大好处就是明确、具体。

    使用测试代码建立目标,编写代码完成测试目标,再制定下一个目标,如此循环,构成了测试驱动开发的工作流程。在接下去的篇幅中,我们开始讨论测试驱动开发中需要注意的一些问题。

    测试必须是自动化的

    和自动化测试相对的是手动测试。手动测试有着自动化测试所没有的优势。最明显的就是简单。任何一位开发人员都能够进行手动测试。即便是用户,也很容易掌握手动测试,他们输入数据,并观察软件的反应(输出),从而判断行为是否正确。大部分的手动测试都是对输入和输出的检验,是一个端到端过程,很能说明问题。

    但是手动测试存在的问题比它所带来的好处要多得多。手动测试可能引入错误,人为的输入错误很容易发生,尤其在数据量大的情况下;大量重复性的手动测试可能成本较高,如果考虑软件发生改动而需要重复手动测试的情况,这个成本还会更高;手动测试的覆盖面不广,只能够测试系统的输入和输出;没有办法对组件进行隔离的测试,从而导致发现问题和解决问题的成本都太高。

    基于上面的讨论,我们应该看到,测试应该做到自动化。虽然一开始测试自动化的成本较高,但是从整个的开发过程来看,自动化测试所产生的价值远远超过其成本。
   
    自动化测试的范围

    那么,到底有哪些东西是需要纳入到自动化测试的范围的呢?例如,对于一个典型的分层应用来说,就有数据库层、数据库访问层,业务逻辑层、界面控制层、界面层。这些层次的测试特点各不相同,哪些应该进行自动化测试呢?最理想的情况是全部。测试一切可能是测试的基本原则,让一切测试都变成自动化则是测试驱动开发的准则。

    应该承认,建立自动化测试需要付出成本,有些自动化测试成本较低,有些则较高。例如,对业务方法的自动化测试相对容易,对关联到数据库的业务方法的测试则繁琐一些,因为你需要处理更多的情况。而界面的自动化测试则较困难,因为界面涉及到大量的人机交互,手动测试是非常简单的,但是自动化测试则相当的困难。

    那么,我们就来看看,像界面测试这样的成本高昂的测试需不需要进行自动化呢?我们拿驻留的Web界面来作为人机交互界面的范例。

    首先,按照分层的原则,界面的层次上不应该拥有业务逻辑,界面层负责的事情是收集用户的动作,将用户的动作请求委托给后端的业务层,并对动作进行响应。所以,和业务相关的逻辑已经被剥离到了业务层了。它的自动化测试属于业务层。同时,我们还发现,在测试的推动下,软件的结构变得更加的合理。

    其次,虽然业务逻辑大量的迁移出界面层,但是界面层中还有状态,还有控制逻辑。这些因素都是和界面的控制的表现息息相关的。既然有逻辑,就需要测试。根据MVC的思路,界面层中包含了模型、视图和控制器。模型是对业务层数据的封装,在J2EE的应用中,可能是普通的JavaBean,也可能是离线的数据封装,或是简单的数据集合。视图负责表现模型,而控制器的职责则比较多,它需要负责处理和检查请求参数,负责调用业务对象并传递请求中所包含的数据,负责创建模型,负责生成视图并把模型传递给它。

    所以,在一个真正的MVC界面设计中,测试的重点在于控制器。模型的测试是很容易的,大部分的模型仅仅包含了数据,所以甚至都不需要测试。一个完美的视图,它应该没有包含任何的逻辑,仅仅只是将模型以某种方式表现出来而已。一个设计优秀的视图,可以很容易的进行替换,而不会造成任何的影响。例如,一个JSP视图可以用一个XSLT视图进行替换。所以,结构合理的视图也是不需要测试的(但对页面要素的检查是必要的)。注意,这里的讨论也同样表明,测试驱动设计向合理的方向发展。

    大部分的控制器都是基于servlet技术的,servlet技术是典型的容器内应用,这使得Junit不容易使用。而Cactus(http://jakarta.apache.org/cactus/index.html)能够解决这个问题,它对Junit进行了扩展,提供了大量的预置功能,简化了request、response、session的使用,Cactus通过Redirector Proxy,建立了一个测试客户端和测试服务端模型,来实现基于容器内的测试。下图是典型的Servlet的结构图:

    Cactus的详细工作机理可以参考这里(http://jakarta.apache.org/cactus/how_it_works.html)。

0
相关文章