,
\.开发研究与设计技术.'. • • • • ~栏目贵任编确:谢攘攘
-
•
试驱动开发探究
曾广平,兰鄂
(中南民族大学,湖北武汉 430074)
摘要:测试驱动开发是与传统程序开发方式相反的一种新型程序设计方法。它以测试编码来推动程序设分,太大缩减 7设计和编码
的冗余度。介绍了测试驱动开发基本思想、本质,并对测试驱动开发过程给出了清晰的流程,总结了测试驱动开发的优点。
关键词:测试驱动开发;极限编程;重构
中国分类号 :TP311 文献标识码 :A
y
文章编号; 1009-3044(2007)13-30172-03
日黯CUSS on Tesf-Driven Development
ZENG Guang-ping, LAN E
(South-Central University for Nationalities, Wuhan 430074, China)
Abstract:Test-Driven development(TDD) ís new pro伊am design method,whichβoppsiteωthe teaditonal program design. The pro萨am
is improved by tescing codes to decrea:;e the redundance of program-designed and programming greatly. The papes íncrodudes TDD though臼
and essences,and recommends TDD developing procedure dearly,Then summariZes excellency ofTDD.
, Key words:Test-Driven development(TDD); eX住eme Programming; Refactor
. ,
, 1 引言
传统软件工程界一直存在这样的看法:程序员不应该测试自
己的代码,原因之一是程序员在代码开发中形成的思维定势将的
导致测试的盲区;其次是从心理学的角度看,个人很难否决自己努
力完成的t作成果。这样,专业的测试团队的出现成为可能,在
现代软件开发过程中,专业测试团队的介入,使得软件的质量得
到了相当程度的保障。测试因此成为对软件质量保证的最后哨
卡,其地位也日渐重要。
尽管在传统测试理论中,测试几乎已经伴随了整个开发周
期,但更多的情况是:至少在项目开始集成之前,测试是基本不存
在的,测试通常是在集成阶段开始进入的,测试人员就不得不在
这个时候在来面对哥德的模块质量闵题、模块设汁问题以及其他
许多早在那之前就应该已经解决的问题。而软件工程的研究显
示:在软件开发周期的越早期发现错误,那个错误将带来的损失
将越小,后期的修改费用会成指数级上升』
为了改变这种趋势,一些新开发方法(敏捷开发方法)都强调
在开发过程的早期就进行频繁地测试 让测试驱动开发的过程,
让对需求的承诺直接的传递到每个组件的开发之中。
2 测试驱动开发
测试号震动开发起源于敏捷开发方法中提倡的测试优先实践。
测试优先实践重视单元测试,强调程序员除了编写代码,还应该
编写单元测试代码。
测试驱动开发 (TDD)改变了编码的过程,并且这种改变不仅
是可能的,同时也是值得去傲的。在开发的l顺序上,采用先编写测
试,再编写代码来满足测试的方法,在实际中有非常好的效果,测
试工作不再是单纯的测试,而成为设计的一部分·。软件开发主要
包括:编写测试用例,编码并进行测试,重构代码以消除建复代码
使其更简洁,这一过理会频繁地迭代,每次进行测试均是为了保
证产品的正确性,设计、编码和测试三者之间的鸿沟将不再存在,
促进了对整个环境和雳求更好地理解。
在编程前,都会先进行设计的工作,不论设 it是糯级还是粗
糙,事实上都是为随后的编码活动制定的标准,这个标准的明确
程度和设计的细致程度有关,但这个标准是不够细化的,因为设
计不可能精细到代码级的程度。既然以设计为导向的标准不够明
确,以代码为臼标的标准才是合适的,因为代码是最明确初具体
的、代码本身就说明了设计。编写测试代码其实是在制定一个小
目标,这个目标非常明确,它规定了需要设计的类、方法以及方法
需要满足的结果。目标制定后.才可以编码来达成该目标。因此
收稿日期,2007-06-20
TDD 是一种细位度的目标管理方法通过明确的目标,推动软件
开发的进行。以这样的思路开发软件,可保证程序中的每→项功
能都有测试来验证它是否正确。
所以要理解 TDD,必须先理解测试。测试是通过对源代码的
运行或别的方式的检测来确定源代码之中是否含有已知或者未
知的错误。 TDD 是在开发前根据对将要开发的程序的要求,先编
写测试代码,并在开发过程中不断地通过运行测试代码来获得所
开发的代码与所要求的结果之间的差距,将这种技术的应用领域
从代码编写扩展到整个开发过程,对整个开发过程的各个阶段进
行测试驱动,首先思考如何对这个阶段进行测试、验证、考核,并
编写相关的测试文档,然后开始下一步t作.最后再验证相关的
工作。这→切,使得软件开发±作的质量一下子变得奋保障了。
3 测试驱动开发本质
TDD 将测试方案设计工作提前,在编写代码之前先做;从测
试的角度来验证设计,锥导设计;同时将测试方案当作行为的准
绳,有效地利用其检验代码编写的每一步,实时验证其正确性,如
此循环送代,实现开发过程的"小步快走"。
TDD 是一种分析方法[1 ,2,3]。在 TDD 中,需求将完全表述为
测试代码。这样,软件设汁师的需求工作就不再是编写用例规约
来覆盖用户的需要,而是迫使仔细思考要做什么和不做什么,特
别是各种例外的情况,并用程序语言正式的写下来,即编写测试
代码来捕获用户的需要。将需求整理为测试代码的形式,只要代
码只要能够通过测试,就代表该代码设计已经能够满足客户需
求。当然,这种肯定是有前提的,即测试代码能够完整的、精确的
描述需求。另外,因为从使用者的角度写出测试代码,除要关心程
序功能的本身外,还要对接口予以足够关注,便于更进一步明确
需求。
TDD 是一种设计方法。它用测试推进设计。必须清晰的定义
程序的界面才能写出测试用例,通过编写测试用例,对其功能分
解、使用过程、接口都要进行了设计。而且,这种从易用性的视角
的代码设汁,通常更符合后揭开发约需求可测试的要求.对代码
的内聚性的提高和复周都非常有益。这样的霜求总是完整的被体
现在了各个模块的接口上,即,保证对接口的使用能够正确无误
的进行,就保证了这个模块的最终质量。
TDD 是一种质量控制方法。开发者在项白的压力下往往忽略
测试,而没有测试的软件增加了开发者的压力,TDD 有一套完整
的单元测试作为回归测试,保证现在能工作的代码将来也能工
作,也可作为集成测试的检查点。 TDD 不是通常意义上的测试技
作者简介:曾广乎(1970-) ,男,湖北人,硕士,讲师,中南民族大学计算机科学学院,主要研究方向:软件工程和数据库技术及应用。
172 钞电脑栩如技术
本栏目责任编喝:谢短短
术,它的目的也不是仅仅用来测试你的代码。 TDD 是把需求分析,
设计,质量控制量化的过程!
TDD 是一种重构和优化的方法。程序员 J总希望代码质量高,
所以会不断地去改进,TDD 确保改进和优化后的质量
TDD 是一种面向对象的开发方法。程序员必须清晰地定义程
序的界面和功能才能写出单元测试用例,而不必关心逻辑是如何
实现。
TDD 是文档。 TDD 是通过写测试来写代码,当用这个视角去
实践 TDD 时,测试用例会细化到以什么条件和参数、调用什么方
法及会有什么返回值等,这就是文档,产生的测试用例代码就是
对代码的最好的解释,说明了代码的运行机制,而且这种程序自
我编挡的方式也促进了交流和反馈。 e
TDD 提供的测试集是信心的来源。快乐工作的基础就是对自
己有信心,对自己的工作成果有信心:不用担心代码是否正确、还
有没有严重 b吨、修改对其他部分有没有影响。
4 测试驱动开发过程
TDD 的基本思想就是在开发功能代码之前,先编写测试代
码。也就是说在明确要开发某个功能后,首先思考如何对这个功
能进行测试,并完成测试代码的编写,然后编写相关的代码满足
这些测试用例、循环进行添加其他功能,直到完全部功能的开发。
其箴言就是"红、绿、重构"[7]:红,创建测试,但无法通过:绿,使测
试被任何必要的方法验证通过:重构,修改代码清楚项目中的冗
余,在确保所有测试仍然可以通过的情况下修正软件的设计。
重掏
院肇测询
绿灯
E遁世所有测试〉
一一寸 I
霞宿更多涮
重掏
室主壁塑
代码功能修复
销写失败测试
图 1 TDD 开发过程
一般依照以下步骤执行(如图1):
红灯
f测试失败〉
(1)理解故事、工作项或者现在使用的特性的需求;
(2)红:创建一个测试,但无法通过;
想象一下新代码是如何被调用的,然后写一个测试,就好像
这段代码已经存在一样:创建新产品代码段,只需要能够编译就
可以了:运行测试:肯定会失败,这是→个标准方法,用来确保测
试调用了正确的代码,或者错误的代码不会偶然正常工作 i这是
一个有意义的失败,也希望它是失败的;
(3)绿 z使测试被任何必要的方法验证通过;
写产品代码并测试通过:验证期望返回值的硬代码:若如期
通过测试,那么可告一段落,"够用就好",如果还需要新功能,那
就再写一个测试,测试通过后继续;测试通过后,运行所有的测
试,以确保一切运转正常。
(4)重构:修改代码以消除冗余,确保所有测试通过的情况下
修正软件的设计;清楚由新增功能造成的冗余:修正设计以优化
整体方案;每次重构后,重新运行所有测试以确保仍被通过 2
σ)重复这一过程。每次重复应该非常短,一个典型的开发时
间段内应该包含许多红/绿/重构过程。,.
5 测试驱动与极限编程
敏捷开发是一种与传统重量级开发方法不同的轻量级开发
方法[4,5],是一种面临迅速变化的需求快速开发软件的能力,作为
其典型代表的极限编程 (XP) ,是以交流、简单、反馈、勇气为价·值
目标的灵巧、严谨、迭代的软件开发方法。它包含多种实践原则,
测试驱动量极限编程(XP)中必不可少的一部分,但任何一种实践
都难以独当重任,需要其他实践来帮助保持平衡(如图 2)。各部分
之间的相互作用则使得整个系统变得丰富起来,XP 中把测试驱
动的设计和开发做到极致。
传统软件开发过程是基于设计之初的正确设计与估计,并通
• • • • .二开发研究与设计技术/
过开发以形成完美的产品,这种开发方式保证了产品的统一性和
之间的最小差异,然而却忽略了交流与反馈,不利于生成错误信息,
测试是得到反馈的一个重要方法,而这也正是 TDD 所倡导的。
阁 2 极限开发中的 τDD
TDD 可以推迟设计决策以简化设计,让精力集中于修正设汁
的问题,有了 TDD,可以有效的避免过度设计和开发 ;TDD 是 XP
中结对编稽的工作模式 :XP 因为采用了, TDD 才能够做到每天的
代码都是产品级代码和每个小型发布都能直接投入使用;有了
TDD ,XP 才能降低风险,去拥抱变化 :XP 才能在计划的时间内完
成计划质量的代码 :XP 才能减少项目成本 ;XP 团队才能对自己
的工作充满自信。
6TDD 与传统测试
TDD 实质是一种编程技术,它间接地确保代码能彻底地被单
元测试检查。但仍然需要参考传统的一些测试比如功能测试、用
户验收测试、系统集成测试等等。一个好的传统测试能发现一个
或多个漏洞,此理论同样适用于 TDD。当一个测试失败时就必须
进行改进,因为需要解决这个问题。更重要的是,当测试不再失败
时,就可以清楚地知道成功了多少。 TDD 增强了信心因为它适应
了预先定下的需求。
像传统的测试一样,项目风险越大,就要更彻底地进行测试,
尽可能测试一切可以测试的东西[6]。传统测试和 TDD 中都不可
能为着完美而奋斗,代之的是测试系统的重要性,应该带着目的
去测试、知道为什么要测试以及测试是在什么等级。 TDD 的代码
测试覆盖率是 1α>%,这是传统测试所不能保证的。
如果为一个功能值得写代码那么一定值得测试。如果不值
得测试,那又何必浪费时间去写一些无用的代码呢?川 , j
TDD 不能替代传统的测试,相反它定义一种验证方式来确保
单元测试的有效性。
7TDD 的优势与误区
TDD 的基本思路就是通过测试来推动整个开发的进行。而
TDD技术并不只是单纯的测试工作。其优点如下:
测试用例提供持久的每个组件仍正常工作的反馈 z 测试用例
扮演同步更新的文档,保证随时可用;当测试通过,产品代码被重
构,清除过冗余时,表明这段代码完成,开发者可以转战下一个测
试;强制严肃的分析和设计,因为开发者在没有正确理解什么是想
要的结果和如何测试的情况下不能创建产品代码;软件倾向去更
好的设计,因为开发者可以自由地做设计决定,并在任何情况下重
构,以保证软件仍在正常工作;测试集扮演一个 Bug 的回溯安全
网:如果发现了一个 Bug,应创建一个测试来看看 Bug 怎么产生
的,然后修改产品代码,消除 Bug,而且其他的测试一切正常;减少
调试时间,加快开发进程;使开发人员重视用户的需求和体验,而
不是仅仅打算实现模块的功能和避免被发现错误 F 能减少编写代
码的恐惧,带来自信:使每一片代码都建立的在扎实的基础之上。
在实施 TDD 要避免如下的情况:基于各种理由不写测试;测
试的跨度过大;测试针对代码而不是针对功能;测试的覆盖面太
小:需要人工干预的测试;复杂的测试配置环境;牵一发而动全身
的测试代码:红灯常明的测试等。
8 结束语
173
\.开发研究与设计技术…
TDD很简单,只要不把它当测试。 TDD 不是一个简单的方法
论,它强调的是质量文化。在 TDD 中,测试方面的技术通常都不
是很新的技术,它们只是简单改变了测试的时机和测试的频率,
但可以保证在软件的设计和开发过程中,始终都要关注测试的问
题。在更高层的视野看来,TDD 事实上是把测试对软件整体质量
的保证引入到软件单元的开发中来,使得整个开发过程中的质量
得到更进一步的监护。这个原则上听起来简单,但当第一次用
四D 开发步骤时它会证明需要更多的训练。没有"放之四海皆准"
的策略,像敏捷开发中的每个事情一样,测试也需要敏捷!
参考文献:
[1] Kent Beck. Test-driven development by example[M]. Addi-
(上接第 171 页)
以下程序用于以同步读写方式打开串口 COM1:
HANDLE hCom ;
DWORD dwError;
hCom=CreateFile("COMl" ,GENERIC-READIGENERIC-
WRlTE,O,NULL,OPEN-EXISTING,O,NULL);
if(hCom== (HANDLE)OxFFFFFFF盯
(dwError=GetLastError();
MessageBox(dwError); }
配置串口
配置串口是通过改变设备控制块 DCB (Device Control Block)
的成员变量值来实现的,接收缓冲区和发送缓冲区的大小可通过
SetupComm 函数来设置。
以下程序将串口设置为:波特率 :96∞,数据位数 :7 位,停止
位 :2 位,偶校验,接收缓冲区和发送缓冲区大小均为 1024 个字
节,最后用 Pl吨eComm 函数终止所有的后台读写操作并清空接收
缓冲区和发送缓冲区。
DCBdcb;
=96∞;
=7;
=EVENP ARITY;
=2;1ffWOSTOPBIT;
TRUE;
=TRUE;
if(! SetCommState(hCom.&dcb))
MessageBox("串口设置(SetCommState)出错!!』");
SetupComm(hCom, 1 024,1 024);
PurgeComm(hCom,PURGE-1χABORTIPURGE-RXABORTI
PURGE一1叉CLEARIPURGE-RXCLEAR);
超时设置
超时设置是通过改变 COMMTIMEOUTS 结构的成员变量值
来实现的。其结构如下:
typedefstruccCOMMTIMEOUτ'S{
DWORDReadlntervalTimeout;
DWORDReadTotalTimeouMtultiplier;
DWORDReadTotalTimeoutConstant;
DWORDWriteTotalTimeoutMtultiplier;
DWORDWriteTotalTimeoutConstant;
}COMMTIMEOU币,*LPCOMMηMEOUTS;
注:ReadlntervalTimeout: 两字符之间最大的延时,当读取串口
数据时,一旦两个字符传输的时间差超过该时间,读取函数将返回
现有的数据。设置为 0 表示该参数不起作用。
ReadTotalTimeoutMtultiplier:读取每字符间的超时。
ReadTotalTimeoutConstant:一次读取串口数据的固定超时。
所以,在]次读取串口的操作过程中,其超时为 ReadTotal-
TimeoutMtultiplier 乘以读取的字节数再加上 ReadTotalTimeout
Constanto 将 Read-IntervalTimeout 设置为 MAXDWORD,并且将
ReadTotalTimeoutMtultiplier 和 ReadTotalTimeoutConstant 设置为
0,表示读取操作将立即返回存放在输入缓冲区的字符。
行4 建1电脑知识与技术
•
本栏目责任编辑:谢嫂嫂
--
50n Wesley publisher, 2∞3 , 95-128.
[2] Kent Beck. Aim,Fire[J]. IEEE Software, 2001 ,18:87-89.
[3] David Astels. Test-driven development[M]. Prentice Hall,
2∞2 , 43-88.
[4] 曾广平,杨喜敏.极限编程架构进化设汁的研究[J].计算机
工程与科学2∞7(3) P139-p141
(5J Kent Beck 著.唐东铭译.解析极限编程一一拥抱变化
[M].人民邮电出版社,2∞.
[6] 蓝庆育,童小念.基于 JavaBean 的构件测试方法的研究
口].中南民族大学学报,2∞6(4).
[7J Junit 官方网站[EB/OL]. h问t忱即tψp:lf附w吼叫叩J扣j川ummn旧i让吨0臼吨rg.
W riteTotalTimeouMtultiplier:写入每字符间的超时。
W riteTotalTimeoutConstant:一次写入串口数据的固定超时。
所以在一次写入串口的操作中,其超时为 WriteTotalTime-
outMtultiplier 乘以写人的字节数再加上 WriteTotalTimeoutCon-
stant。
配置串口和超时设置也可以用 BuildDCBAndTimeouts 函数一
次完成,该函数原型如下:
BuildDCBAndTimeouts(lpszString,&dcb,&timeouts)
数据读写
对串口进行读取、写人所用的函数和对文件进行读取、写人
所用的函数相同,具体实现可采用查询、同步、异步或事件驱动等
方法,对串口进行读取、写人所用的函数原型如下:
ReadFile(hCom,inBu缸~nBytes.&nBytesRead.&overlapped);
WriteFile(hCom,outBuff,nToWrite,&actualWrite,&overlapped);
关闭串口
利用 API 函数实现串口通信时关闭串口非常简单,只需使用
CreateFile 函数返回的句柄作为参数调用 CloseHandle 即可:
CloseHandle(hCom) ;
4 Mscomm 控件的应用
Msωmm 控件通过串行端口传输和接收数据,为应用程序提
供串行通讯功能。 Mscomm 控件在串口编程时非常方便,程序员不
必花时间去了解较为复杂的 API 函数,而且在 VC, VB, Delphi 等
语言中均可使用。实际上,采用 Mscomm 控件是间接地调用了 API
函数,其执行过程比直接调用 API 要复杂,但对程序员来说,在程
序实现上却简单得多。 Windows 使用通信驱动程序 Comm. drv ,以
便于应用程序能够使用标准的 Windows API 函数来传递和接收
数据。使用 M配omm 控件,程序员只需要掌握如何用 Mscomm 所
提供的属性或事件,以驱动 API 函数接口完成。
5 结束语
以上列举了两种可在 VC++编程中使用的串口通信方法,其中
利用 API 函数实现串口通信的编程方法功能强大,灵活性好,但
原理上比较复杂,需要编程人员对串口的硬件工作原理有较深入
的了解;利用 Mscomm 控件实现串口通信的编程方法原理简单;
很容易实现,但编程的灵活性较差。总之,以上串口通信的编程方
法在使用条件、灵活性和复杂程度等方面各有优缺点,分别适合
于各自不同的应用场合。
参考文献:
[1]吴华,岳晋生. WindowsNTWin32 软件开发使用详解[MJ.北
京:电子工业出版社,1995.
[2J刘树坤.用 Windows API 实现串行通信口].计算机应用:
2创则, (2):63-66.
[3]刘小石.精通 VisualC++ [M].北京:清华大学出版社,
2α)().
[4J李华.单片机通用接口技术[MJ.北京:北京航空航天大学出
版社,1999.