??xml version="1.0" encoding="utf-8" standalone="yes"?>
{关键字}
试驱动开?Test Driven Development/TDD
试用例/TestCase/TC
设计/Design
重构/Refactoring
{TDD的目标}
Clean Code That Works
q句话的含义是,事实上我们只做两件事情:让代码奏效(WorkQ和让代码洁净QCleanQ,前者是把事情做对,后者是把事情做好。想想看Q其实我们^时所做的所有工作,除去无用的工作和错误的工作以外,真正正确的工作,q且是真正有意义的工作,其实也就只有两大c:增加功能和提升设计,而TDD 正是在这个原则上产生的。如果您的工作ƈ非我们想象的q样Q(q意味着您还存在W三cL有意义的工作,或者您所要做的根本和我们在说的是两回事)Q那么这告诉我们您ƈ不需要TDDQ或者不适用TDD。而如果我们偶然猜对(q对于我来说是偶Ӟ而对于Kent Beck和Martin Fowlerq样的大师来说则是辛勤工作的成果Q,那么恭喜您,TDD有可能成为您显著提升工作效率的一件法宝。请不要信疑Q若卌,因ؓM一Ҏ的技术——只要是从根本上改变人的行ؓ方式的技术——就必然使得怿它的来越怿Q不信的来越不信。这好比学游泳Q唯一能学会游泳的途径是亲自下去游,除此之外别无他法。这也好比成功学Q即使把卡耐基或希博士的书倒背如流也不能拥有积极的心态,可当你以U极的心态去成就了一番事业之后,你就再也M开它了。相信我QTDD也是q样Q想试用TDD的h们,请遵循下面的步骤Q?/p>
~写TestCase --> 实现TestCase --> 重构 Q确定范围和目标Q?/td> Q增加功能) Q提升设计)
[友情提示Q敏捷徏模中的一个相当重要的实践被称为:Prove it With CodeQ这U想法和TDD不谋而合。]
{TDD的优点}
『充满吸引力的优炏V?/strong>
『不显而易见的优点?/strong>
『有争议的优炏V?/strong>
~写TestCase --> 实现TestCase --> 重构 Q不可运行) Q可q行Q?/td> Q重构)
步骤 | 制品 |
Q?Q快速新增一个测试用?/td> | 新的TestCase |
Q?Q编译所有代码,刚刚写的那个试很可能编译不通过 | 原始的TODO List |
Q?Q做可能少的改动,让编译通过 | Interface |
Q?Q运行所有的试Q发现最新的试不能~译通过 | Q?Red Bar) |
Q?Q做可能少的改动,让测试通过 | Implementation |
Q?Q运行所有的试Q保证每个都能通过 | Q?Green Bar) |
Q?Q重构代码,以消除重复设?/td> | Clean Code That Works |
{FAQ}
[什么时候重构?]
如果您在软g公司工作Q就意味着您成天都会和想通过重构改善代码质量的想法打交道Q不仅您如此Q您的大部分同事也都如此。可是,I竟什么时候该重构Q什么情况下应该重构呢?我相信您和您的同事可能有很多不同的看法,最常见的答案是“该重构时重构”Q?#8220;写不下去的时候重?#8221;Q和“下一ơP代开始之前重?#8221;Q或者干脆就?#8220;最q没旉Q就不重构了Q下ơ有旉的时候重构吧”。正如您已经预见到我惌的——这些想法都是对重构的误解。重构不是一U构Y件的工具Q不是一U设计Y件的模式Q也不是一个Y件开发过E中的环节,正确理解重构的h应该把重构看成一U书写代码的方式Q或习惯Q重构时时刻L可能发生。在TDD中,除去~写试用例和实现测试用例之外的所有工作都是重构,所以,没有重构M设计都不能实现。至于什么时候重构嘛Q还要分开看,有三句话是我的经验:实现试用例旉构代码,完成某个Ҏ时重构设计Q品的重构完成后还要记得重构一下测试用例哦?/p>
[什么时候设计?]
q个问题比前面一个要隑֛{的多,实话实说Q本人在依照TDD开发Y件的时候也常常被这个问题困扎ͼL觉得有些问题应该在写试用例之前定下来,而有些问题应该在新增一个一个测试用例的q程中自然出玎ͼ水到渠成。所以,我的是,设计的时机应该由开发者自己把握,不要受到TDD方式的限Ӟ但是Q不需要事先确定的事一定不能事先确定,免得捆住了自q手脚?/p>
[什么时候增加新的TestCaseQ]
没事做的时候。通常我们认ؓQ如果你要增加一个新的功能,那么先写一个不能通过?TestCaseQ如果你发现了一个bugQ那么先写一个不能通过的TestCaseQ如果你现在什么都没有Q从0开始,请先写一个不能通过?TestCase。所有的工作都是从一个TestCase开始。此外,q要注意的是Q一些大师要求我们每ơ只允许有一个TestCase亮红灯,在这?TestCase没有Green之前不可以写别的TestCaseQ这U要求可以适当考虑Q但即有多个TestCase亮红灯也不要紧,q未q反TDD 的主要精?/p>
[TestCase该怎么写?]
试用例的编写实际上是两个q程Q用尚不存在的代码和定义这些代码的执行l果。所以一?TestCase也就应该包括两个部分——场景和断言。第一ơ写TestCase的h会有很大的不适应的感觉,因ؓ你之前所写的所有东襉K是在解决问题Q现在要你提出问题确实不大习惯,不过不用担心Q你正在做正的事情Q而这个世界上最隄事情也不在于如何解决问题Q而在于ask the right questionQ?/p>
[TDD能帮助我消除Bug吗?]
{:不能Q千万不要把“试”?#8220;除虫”混ؓ一谈!“除虫”是指E序员通过自己的努力来减少bug的数量(消除bugq样的字眼我们还是不要讲为好^_^Q,?#8220;试”是指E序员书写品以外的一D代码来保产品能有效工作。虽然TDD所~写的测试用例在一定程度上为寻找bug提供了依据,但事实上Q按照TDD的方式进行的软g开发是不可能通过TDD再找到bug的(x我们前面说的“完工时完?#8221;Q,你想啊,当我们的代码完成的时候,所有的试用例都亮了绿灯,q时隐藏在代码中的bug一个都不会露出马脚来?/p>
但是Q如果要?#8220;试”?#8220;除虫”之间有什么联p,我相信还是有很多话可以讲的,比如TDD事实上减了bug的数量,把查找bug战役的关注点从全U战场提升到代码战场以上。还有,bug的最可怕之处不在于隐藏之深Q而在于满天遍野。如果你发现了一个用户很不容易才能发现的bugQ那么不一定对工作做出了什么杰A献,但是如果你发CD代码中Qbug的密度或LE度q高Q那么恭喜你Q你应该抛弃q写这D代码了。TDD避免了这U情况,所以将Lbug的工作降低到了一个新的低度?/p>
[我该Z个Feature~写TestCaseq是Z个类~写TestCaseQ]
初学者常问的问题。虽然我们从TDD 的说明书上看到应该ؓ一个特性编写相应的TestCaseQ但Z么著名的TDD大师所写的TestCase都是和类/Ҏ一一对应的呢Qؓ了解释这个问题,我和我的同事们都做了很多试验Q最后我们得C一个结论,虽然我不知道是否正确Q但是如果您没有{案Q可以姑且相信我们?/p>
我们的研I结果表明,通常在一个特性的开发开始时Q我们针对特性编写测试用例,如果您发现这个特性无法用TestCase表达Q那么请这个特性细分,直至您可以ؓ手上的特性写出TestCase为止。从q里开始是最安全的,它不会导致Q何设计上重大的失误。但是,随着您不断的重构代码Q不断的重构 TestCaseQ不断的依据TDD的思想做下去,最后当产品伴随试用例集一起发布的时候,您就会不l意的发现经q重构以后的试用例很可能是和品中的类/Ҏ一一对应的?/p>
[什么时候应该将全部试都运行一遍?]
Good QuestionQ大师们要求我们每次重构之后都要完整的运行一遍测试用例。这个要求可以理解,因ؓ重构很可能会改变整个代码的结构或设计Q从而导致不可预见的后果Q但是如果我正在开发的是一个ERP怎么办?q行一遍完整的试用例可能花Ҏ个小Ӟ况且现在很多重构都是由工具做到的Q这个要求的可行性和前提条g都有所动摇。所以我认ؓ原则上你可以挑几个你觉得可能受到本次重构影响的TestCase去runQ但是如果运行整个测试包只要p数秒的时_那么不介意你按大师的要求d?/p>
[什么时候改q一个TestCaseQ]
增加的测试用例或重构以后的代码导致了原来的TestCase的失M效果Q变得无意义Q甚臛_能导致错误的l果Q这时是改进TestCase的最好时机。但是有时你会发玎ͼq样做仅仅导致了原来的TestCase在设计上是臃肿的Q或者是冗余的,q都不要紧,只要它没有失效,你仍然不用去改进它。记住,TestCase不是你的产品Q它不要好看Q也不要怎么太科学,甚至没有性能要求Q它只要能完成它的命就可以了——这也证明了我们后面所说的“用Ctrl-C/Ctrl-V~写试用例”的可行性?/p>
但是Q美国h的想法其实跟我们q是不太一P拿托巴赞的MindMap来说吧,其实画MindMap只是Z表现自己的思\Q或记忆某些重要的事情,但托却大家把MindMapL一件艺术品Q甚臌有很多艺术家把自q的抽象派MindMap拿出来帮助托做宣传。同P大师们也要求我们把TestCase写的跟代码一栯量精良,可我惌的是Q现在国内有几个公司能把产品的代码写的精良?Q还是一步一步慢慢来吧?/p>
[Z么原来通过的测试用例现在不能通过了?]
q是一个警报,Red AlertQ它可能表达了两层意思——都不是什么好意思—?Q你刚刚q行的重构可能失败了Q或存在一些错误未被发玎ͼ臛_重构的结果和原来的代码不{h了?Q你刚刚增加的TestCase所表达的意思跟前面已经有的TestCase相冲H,也就是说Q新增的功能q背了已有的设计Q这U情况大部分可能是之前的设计错了。但无论哪错了,无论是那层意思,x到这个问题的Ҏ都比TDD的正常工作要难?/p>
[我怎么知道那里该有一个方法还是该有一个类Q]
q个问题也是常常出现在我的脑中Q无Z是第一ơ接触TDD或者已l成?TDD专家Q这个问题都会缠l着你不放。不q问题的{案可以参考前面的“什么时候设?#8221;一节,{案不是唯一的。其实多数时候你不必考虑未来Q今天只做今天的事,只要有重构工P从方法到cd从类到方法都很容易?/p>
[我要写一个TestCaseQ可是不知道从哪里开始?]
从最重要的事开始,what matters mostQ从脚下开始,从手头上的工作开始,从眼前的事开始。从一个没有UI的核心特性开始,从算法开始,或者从最有可能耽误旉的模块开始,从一个最严重的bug开始。这是TDDM者和鼠目寸光者的一个共同点Q不同点是前者早已成竹在胸?/p>
[Z么我的测试L看v来有Ҏ蠢?]
哦?是吗Q来Q握个手Q我的也是!不必担心q一点,事实上,大师们给的例子也相当愚蠢Q比如一个极端的例子是要写一个两个int变量相加的方法,大师先断a2+3=5Q再断言5+5=10Q难道这些代码不是很愚蠢吗?其实q只是一个极端的例子Q当你初ơ接触TDDӞ写这L代码没什么不好,以后当你熟练时就会发现这样写没必要了Q要CQ谦虚是通往TDD的必l之路!从经典开发方法{向TDD像从面向过E{向面向对象一样困难,你可能什么都懂,但你写出来的cL有一个纯OO的!我的同事q告诉我真正的太极拳Q其速度是很快的Q不比Q何一个快拌慢,但是初学者(通常是指学习太极拳的?0q_太不Ҏ把每个姿劉K做对Q所以只能慢慢来?/p>
[什么场合不适用TDDQ]
问的好,实有很多场合不适合使用TDD。比如对软g质量要求极高的军事或U研产品——神州六P人命兛_的Y件——医疗设备,{等Q再比如设计很重要必L前做好的软gQ这些都不适合TDDQ但是不适合TDD不代表不能写TestCaseQ只是作用不同,C不同|了?/p>
{Best Practise}
[微笑面对~译错误]
学生时代最x的是~译错误Q编译错误可能会被老师视ؓ上课不认真听评证据Q或者同学间怺嘲笑的砝码。甚至离开学校很多q的老程序员依然x它像x迟CP潜意识里g~译错误极有可能和工资挂钩(或者和智商挂钩Q反正都不是什么好事)。其实,只要提交到版本管理的代码没有~译错误可以了Q不要担心自己手上的代码的编译错误,通常Q编译错误都集中在下面三个方面:
Q?Q你的代码存在低U错?br>Q?Q由于某些Interface的实现尚不存在,所以被试代码无法~译
Q?Q由于某些代码尚不存在,所以测试代码无法编?br>h意第二点与第三点完全不同Q前者表明设计已存在Q而实C存在D的编译错误;后者则指仅有TestCase而其它什么都没有的情况,设计和实现都不存在,没有Interface也没有Implementation?/p>
另外Q编译器q有一个优点,那就是以最敏捷的n手告诉你Q你的代码中有那些错误。当然如果你拥有Eclipseq样可以及时提示~译错误的IDEQ就不需要这L功能了?/p>
[重视你的计划清单]
在非TDD的情况下Q尤其是传统的瀑布模型的情况下Q程序员不会不知道该做什么,事实上,L有设计或者别的什么制品在引导E序员开发。但是在TDD的情况下Q这U优势没有了Q所以一个计划清单对你来说十分重要,因ؓ你必自己发现该做什么。不同性格的h对于q一点会有不同的反应Q我怿qx做事没什么计划要依靠别h安排的hQ所谓将才)可能略有不适应Q不q不要紧QTasks和CalendarQ又U效率手册)早已成ؓC上班族的必备工具了;而^时工作生zd很有计划性的人,比如?)Q就会更喜欢q种自己可以掌控Plan的方式了?/p>
[废黜每日代码质量查]
如果我没有记错的话,PSP对于个h代码查的要求是蛮严格的,而同h在针对个人的问题上, TDD却徏议你废黜每日代码质量查,别v疑心Q因ZL在做TestCase要求你做的事情,q且L有办法(自动的)查代码有没有做到q些事情 ——红灯停l灯行,所以每日代码检查的旉可能被节省,对于一个严格的PSP实践者来_q个成本q是很可观的Q?/p>
此外Q对于每日代码质量检查的另一个好处,是帮助你认识自q代码Q全面的从宏观、微观、各个角度审视自q成果Q现在,当你依照TDD做事Ӟq个优点也不需要了Q还记得前面说的TDD的第二个优点吗,因ؓ你已l全面的使用了一遍你的代码,q完全可以达到目的?/p>
但是Q问题往往也ƈ不那么简单,现在有没有h能告诉我Q我如何全面审视我所写的试用例呢?别忘了,它们也是以代码的形式存在的哦。呵呵,但愿q个问题没有把你吓到Q因为我怿到目前ؓ止,它还不是瓉问题Q况且在~写产品代码的时候你q是会自ȝ发现很多试代码上的没考虑到的地方Q可以就此修改一下。道理就是如此,世界上没有Q何方法能代替你思考的q程Q所以也没有MҎ能阻止你犯错误,TDD仅能让你更容易发现这些错误而已?/p>
[如果无法完成一个大的测试,׃最的开始]
如果我无法开始怎么办,教科书上有个很好的例子:我要写一个电影列表的c,我不知道如何下手Q如何写试用例Q不要紧Q首先想象静态的l果Q如果我的电影列表刚刚徏立呢Q那么它应该是空的,OKQ就写这个断a吧,断言一个刚刚初始化的电影列表是I的。这不是愚蠢Q这是细节,奥运会五全能的金牌得主玛丽?#183;金是q样说的Q?#8220;成功人士的共同点在于……如果目标不够清晰Q他们会首先做通往成功道\上的每一个细步?#8230;…”?/p>
[试~写自己的xUnit]
Kent Beck大家每当接触一个新的语a或开发^台的时候,p己写q个语言或^台的xUnitQ其实几乎所有常用的语言和^台都已经有了自己?xUnitQ而且都是大同异Q但是ؓ什么大师给Zq样的徏议呢。其实Kent Beck的意思是说通过q样的方式你可以很快的了解这个语a或^台的Ҏ,而且xUnit实很简单,只要知道原理很快p写出来。这对于那些喜欢自己写底层代码的人,或者喜Ƣ控制力的h而言是个好消息?/p>
[善于使用Ctrl-C/Ctrl-V来编写TestCase]
不必担心TestCase会有代码冗余的问题,让它冗余好了?/p>
[永远都是功能FirstQ改q可以稍后进行]
上面q个标题q可以改成另外一句话Q避免过渡设计!
[淘汰陈旧的用例]
舍不得孩子套不着狹{不要可惜陈旧的用例Q因为它们可能从概念上已l是错误的了Q或仅仅会得出错误的l果Q或者在某次重构之后失去了意义。当然也不一定非要删除它们,从TestSuite中除去(JUnitQ或加上IgnoredQNUnitQ标{也是一个好办法?/p>
[用TestCase做试验]
如果你在开始某个特性或产品的开发之前对某个领域不太熟悉或一无所知,或者对自己在该领域里的能力一无所知,那么你一定会选择做试验,在有单元试作工L情况下,你用TestCase做试验,q看h像你在写一个验证功能是否实现的 TestCase一P而事实上也一P只不q你所验证的不是代码本w,而是q些代码所依赖的环境?/p>
[TestCase之间应该量独立]
保证单独q行一个TestCase是有意义的?/p>
[不仅试必须要通过的代码,q要试必须不能通过的代码]
q是一个小技巧,也是不同于设计思\的东ѝ像界的值或者ؕ码,或者类型不W的变量Q这些输入都可能会导致某个异常的抛出Q或者导致一个标C?#8220;illegal parameters”的返回|q两U情况你都应该测试。当然我们无法枚举所有错误的输入或外部环境,q就像我们无法枚举所有正的输入和外部环境一P只要TestCase能说明问题就可以了?/p>
[~写代码的第一步,是在TestCase中用Ctrl-C]
q是一个高U技巧,呃,是的Q我是这个意思,我不是说q个技巧难以掌握,而是说这个技巧当且仅当你已经是一个TDD高手Ӟ你才能体会到它的力。多ơ用TDD的h都有q样的体会,既然我的TestCase已经写的很好了,很能说明问题Qؓ什么我的代码不能从TestCase拯一些东西来呢。当Ӟq要求你的TestCase已经h很好的表达能力,比如断言f (5)=125的方式显然没有断af(5)=5^(5-2)表达更多的内宏V?/p>
[试用例包应该尽量设计成可以自动q行的]
如果产品是需要交付源代码的,那我们应该允许用户对代码q行修改或扩充后在自q环境下run整个试用例包。既焉常情况下的产品是可以自动运行的Q那Z么同样作Z付用L制品Q测试用例包׃是自动运行的呢?即产品不需要交付源代码Q测试用例包也应该设计成可以自动q行的,qؓ试部门或下一版本的开发h员提供了极大的便利?/p>
[只亮一盏红灯]
大师的徏议,前面已经提到了,仅仅是徏议?/p>
[用TestCase描述你发现的bug]
如果你在另一个部门的同事使用了你的代码,q且Q他发现了一个bugQ你猜他会怎么做?他会立即走到你的工位边上Q大声斥责说Q?#8220;你有bugQ?#8221;吗?如果他胆敢这样对你,对不P你一定要冷静下来Q不要当面回骂他Q相反你可以微微一W,然后心^气和的对他说Q?#8220;哦,是吗Q那么好吧,l我一个TestCase证明一下?#8221;现在局势已l倒向你这一边了Q如果他q没有准备好回答你这致命的一击,我猜他会感到非常愧Qƈ在内心责怪自己太莽撞。事实上Q如果他的TestCase没有q多的要求你的代码(而是按你们事前的契约Q,q且亮了U灯Q那么就可以定是你的bugQ反之,Ҏ则无理了。用TestCase描述bug的另一个好处是Q不会因Z后的修改而再ơ暴露这个bugQ它已经成ؓ你发布每一个版本之前所必须查的内容了?/p>
{关于单元试}
单元试的目标是
Keep the bar green to keep the code clean
q句话的含义是,事实上我们只做两件事情:让代码奏效(Keep the bar greenQ和让代码洁净QKeep the code cleanQ,前者是把事情做对,后者是把事情做好,两者既是TDD中的两顶帽子Q又是xUnit架构中的因果关系?/p>
单元试作ؓ软g试的一个类别,q是xUnit架构创造的Q而是很早有了。但是xUnit架构使得单元试变得直接、简单、高效和规范Q这也是单元试最q几q飞速发展成量一个开发工具和环境的主要指标之一的原因。正如Martin Fowler所_“软g工程有史以来从没有如此众多的人大大收益于如此单的代码Q?#8221;而且多数语言和^台的xUnit架构都是大同异Q有的仅是语a不同Q其中最有代表性的是JUnit和NUnitQ后者是前者的创新和扩展。一个单元测试框架xUnit应该Q?Q每个TestCase独立q行Q?Q每个TestCase可以独立和报告错误Q?Q易于在每次q行之前选择TestCase。下面是我枚丑և的xUnit框架的概念,q些概念构成了当前业界单元测试理论和工具的核心:
[试Ҏ/TestMethod]
试的最单位,直接表示Z码?/p>
[试用例/TestCase]
由多个测试方法组成,是一个完整的对象Q是很多TestRunner执行的最单位?/p>
[试容器/TestSuite]
由多个测试用例构成,意在把相同含义的试用例手动安排在一PTestSuite可以呈树状结构因而便于管理。在实现ӞTestSuite形式上往往也是一个TestCase或TestFixture?/p>
[断言/Assertion]
断言一般有三类Q分别是比较断言Q如assertEqualsQ,条g断言Q如isTrueQ,和断a工具Q如failQ?/p>
[试讑֤/TestFixture]
为每个测试用例安排一个SetUpҎ和一个TearDownҎQ前者用于在执行该测试用例或该用例中的每个测试方法前调用以初始化某些内容Q后者在执行该测试用例或该用例中的每个方法之后调用,通常用来消除试对系l所做的修改?/p>
[期望异常/Expected Exception]
期望该测试方法抛出某U指定的异常Q作Z?#8220;断言”内容Q同时也防止因ؓ合情合理的异常而意外的l止了测试过E?/p>
[U类/Category]
为测试用例分c,实际使用时一般有TestSuite׃再用CategoryQ有Category׃再用TestSuite?/p>
[忽略/Ignored]
讑֮该测试用例或试Ҏ被忽略,也就是不执行的意思。有些被抛弃的TestCase不愿删除Q可以定为Ignored?/p>
[试执行?TestRunner]
执行试的工P表示以何U方式执行测试,别误会,q可不是在代码中规定的,完全是与试内容无关的行为。比如文本方式,AWT方式Qswing方式Q或者Eclipse的一个视囄{?/p>
{实例QFibonacci数列}
下面的Sample展示TDDer是如何编写一个旨在生Fibonacci数列的方法?br>Q?Q首先写一个TCQ断afib(1) = 1;fib(2) = 1;q表C数列的第一个元素和W二个元素都??/p>
Q?Q上面这D代码不能编译通过QGreatQ——是的,我是说GreatQ当Ӟ如果你正在用的是Eclipse那你不需要编译,Eclipse 会告诉你不存在fibҎQ单击mark会问你要不要新徏一个fibҎQOhQ当ӞZ让上面那个TC能通过Q我们这样写Q?/p>
Q?Q现在那个TC亮了l灯QwowQ应该庆一下了。接下来要增加TC的难度了Q测W三个元素?/p>
不过q样写还不太好看Q不如这样写Q?/p>
Q?Q新增加的断aD了红灯,Z扭{q一局势我们这样修改fibҎQ其中部分代码是从上面的代码中Ctrl-C/Ctrl-V来的Q?/p>
Q?Q天哪,q真是个׃h写的代码Q是啊,不是吗?因ؓTC是产品的蓝本,产品只要恰好满TCok。所以事情发展到q个地步不是fibҎ的错Q而是TC的错Q于是TCq要q一步要求:
Q?Q上有政{下有对{?/p>
Q?Q好了,不玩了。现在已l不是贱不贱的问题了Q现在的问题是代码出C冗余Q所以我们要做的是——重构:
Q?Q好Q现在你已经fibҎ已经写完了吗Q错了,一个危险的错误Q你忘了错误的输入了。我们o0表示Fibonacci中没有这一V?/p>
then change the method fib to make the bar greanQ?/p>
Q?Q下班前最后一件事情,把TC也重构一下:
Q?0Q打完收工?/p>
{关于本文的写作}
在本文的写作q程中,作者也用到了TDD的思维Q事实上作者先构思要写一什么样的文章,然后写出q篇文章应该满的几个要求,包括功能的要求(要写些什么)和性能的要求(可读性如何)和质量的要求Q文字的要求Q,q些要求起初是一个也达不到的Q因为正文还一个字没有Q,在这U情况下作者的文章无法~译通过Qؓ了达到这些要求,作者不停的写啊写啊Q终于在花尽了两个月的心血之后完成了当初既定的所有要求(make the bar greenQ,随后作者整理了一下文章的l构Q重构)Q在满意的提交给了Blogpȝ之后Q作者穿上了一件绿色的汗衫Q趴在地上,学了两声青蛙叫。。。。。。。^_^
{后记QMartin Fowler在中国}
从本文正式完成到发表的几个小旉Q我偶然d了Martin Fowler先生北京访谈录,光提到了很多对试驱动开发的看法Q摘抄在此:
Martin FowlerQ当Ӟ值得׃半的旉来写单元试Q!因ؓ单元试能够使你更快的完成工作。无数次的实践已l证明这一炏V你的时间越是紧张,p要写单元试Q它看上LQ但实际上能够帮助你更快、更舒服地达到目的?br>Martin FowlerQ什么叫重要Q什么叫不重要?q是需要逐渐认识的,不是惛_然的。我为绝大多数的模块写单元测试,是有点烦人,但是当你意识到这工作的h值时Q你会欣然的?br>Martin FowlerQ对全世界的E序员我都是那么几条Q?#8230;…W二Q学习测试驱动开发,q种新的Ҏ会改变你对于软g开发的看法?#8230;…
——《程序员》,2005q?月刊
{鸣谢}
fhawk
Dennis Chen
般若菩提
Kent Beck
Martin Fowler
c2.com
准备工作Q?/span>
1?nbsp;安装perl/Python
2?nbsp;下蝲解压CxxTest
3?nbsp;讄环境变量Q?/span>
如果使用Perl,则设|一个名?span>PERL的环境变量,gؓperl.exe的位|。(比如D:\Perl\Bin\Perl.exeQ;
如果安装Python,则设|一个名?span>PYTHON的环境变量,gؓ你安装的Python目录下的python.exe路径Q比?span>D:\Python25\python.exeQ;
讄一个名?span>CXXTESTDIR 的环境变量,gؓCxxTest解压后的目录。(比如D:\CxxtestQ。(讄CXXTESTDIR的原因:免去每次?span>makefile文g中指?span>CXXTESTDIR的目录)
方式一Q?span>CxxTest \sample\msvc中的框架
1?nbsp;拯msvc文g夹到工程目录?/span>
打开CxxTest_Workspace.slnQ可以看C个项目:
- CxxTest_3_Generate q行 cxxtestgen.pl 生成 runner.cpp 文gQ?/span>
- CxxTest_2_Build ~译生成?/span>runner.cpp文gQ?/span>
- CxxTest_1_Run q行试Q?/span>
2?nbsp;修改其中?span>makefile文g:
1Q将以下内容Q?/span>
# Where to look for the tests
TESTS = ..\gui\*.h ..\*.h
修改为:
# Where to look for the tests
TESTS = *.h
q段话的作用是查找测试文Ӟ我们的测试文件是.h格式的,攑֜当前目录下。(如果?span>.hpp格式的话Q自然改?span>*.hppQ放在其它目录的话,q要修改路径Q?/span>
2Q将以下内容删除Q?/span>
# Where the CxxTest distribution is unpacked
CXXTESTDIR = ..\..
因ؓ先前已经定义?span>CXXTESTDIR的环境变量,q里的定义可以省掉?/span>
3?nbsp;?/span>CxxTest_3_Generate目中添加测试文?/span>
q是试文g的一个简单的例子Q?/span>
// Sampletest.h
#include <cxxtest/TestSuite.h>
//定义一个测试套件类Q将试用例攑օ其中
class SampletestSuite : public CxxTest::TestSuite
{
public:
//定义试Q以test作ؓ试函数前缀Q?/span>
//q是cxxtestgen.pl?span>cxxtestgen.pyҎ试文件进行扫描,抽取试用例的依?/span>
void testMultiplication( void )
{
TS_ASSERT_EQUALS( 2 * 2, 5 );
}
};
4?nbsp;q行试Q即生成CxxTest_1_RunQ可在输出窗口看到测试结果,q双ȝ果行可定位到源代码?/span>
Ҏ二:?span>msvcQ将GenerateQ?/span>BuildQ?/span>Run集成C个项目中
1?nbsp;在工E中d新项目:Unit_Test
修改后?span>makefile文g拯?span>Unit_Test目录中?/span>
2?nbsp;?span>Unit_Test目源文件中d新徏?span>dummy
无论dq来的是.cpp,q是.h,后~名去掉?/span>
叛_dummyQ选择属性,在弹出的属性页中,“自定义生成步?#8221;选项->“命o?#8221;选项Q输入:
ECHO Don't Delete this file!!! > Dummy
ECHO -
ECHO -
ECHO -------------------- Generating test cases --------------------
if exist runner.cpp
NMAKE runner.cpp /nologo
ECHO -
ECHO –
?#8220;输出”选项中输入:
Runner.cpp
Dummy只是一个文Ӟ里边可以是Q何内容,关键点在于对Dummy文gq行~译的时候所执行的操作,q是一个批处理文gQ这个批处理文g执行生成 testcase 的操作,也就是调?span>Python/Perl生成试用例Q也是runner.cpp文gQ。ؓ了保持每ơ编译都可以生成新的runner文gQ就必须保证
1Q?span>Dummy文g?span>runner.cpp之前q行~译Q?/span>
2Q?span>Dummy文g每次都必被重新~译?/span>
W一Ҏ通过文g序来的Q第二点是在生成事g里边重新生成Dummy文g来保证的Q?span>VS2005g没问题,VC6gq是有些问题的)?/span>
从另外一个角度讲Q这?span>Dummy文g相当?span> samples/msvc W一和第二个目的功能。(?/span>Generate?/span>BuildQ?/span>
3?nbsp;~译dummy文gQ生?span>runner.cppQ将其添加到Unit_Test“源文?#8221;中?/span>
定当前目录中已?span>.h试文gQ否则可能提C错误?/span>
4?nbsp;叛_Unit_TestQ选择属性,在弹出来的属性页中, “生成事g”选项->“生成后事?span>”选项->“命o?#8221;选项中输入:
ECHO -
ECHO -
ECHO -------------------- Running Unit Test Cases --------------------
$(OutDir)\$(TargetName).exe
ECHO -
ECHO -
ECHO -------------------------------------------------------------------------------
攑֜目“生成后事?#8221;中的q一D命令,q行生成的测试,相当?/span>samples/msvc W三个项目的功能。(?/span>RunQ?/span>
5?nbsp;?span>Unit_Test“头文?#8221;中,d新的.h试文g?/span>
生成Unit_TestQ即可在输出H口分割U下看到q行试的结果,双击l果行可以定位到源代码?/span>
如:
1>-------------------- Running Unit Test Cases --------------------
1>Running 1 test
1>In MathsTestSuite::testMultiplication:
1>f:\test\cxxunittest\cxxunittest\sampletest.h(9): Error: Expected (2 * 2 == 5), found (4 != 5)
1>Failed 1 of 1 test
1>Success rate: 0%
1>----------------------------------------------------------------------------------
Ҏ三:抽取Z?span>Unit_Test文g?/span>
所谓方法三Q不q是方法二中的Unit_Test文g夹,替代Ҏ一中的msvc文g夹,Ud到其它的目中用而已?/span>
不像Ҏ一?span>msvc那样需要三个项目,也不需像方法二那样Q每ơ都修改dummyQ和目属性,d命o行。将Unit_Test文g夹各个属性都配置好之后,独立出来随时备用?/span>
准备工作Q如前所a?/span>
要进行测试时Q?/span>
?/span>Unit_test文gҎ贝放到工E目录中Q在工程中添加里面的Unit_Test目Q仿?/span>Sampletest.h写自q试文g?/span>
q行试Ӟ
先编?/span>dummyQ再生成该测试项目?/span>(如果没有另外d*.h文gQ直接生成就可以?/span>)单元试现在已经成ؓ标准的编E实践,但是C++~少Java?/span>.Netq_语言的反机Ӟ所以无法枚举测试方法,必须手工dQ或者用一些特别的宏,弄得代码非常隄?/span>Java语言单元试?/span>JUnit的天下,C#基本上都?/span>NUnitQ?/span>C++则群花怒放Q单元测试框枉常多Q?/span>JUnitULq来?/span>CppUnitQ?/span>Boost::testQ?/span>CppTestQ?/span>CxxTestQ?/span> TUT{等。但是解x案最好的?/span>CxxTest?/span>TUTQ?/span>CxxTest采用的方法比较特D,?/span>Perl分析C++的源文gQ从中抽取测试方法,创徏TestSuite。语法与JUnit非常怼Q没有用高U的C++Ҏ,也没有定义特别的宏,无须写额外的代码?/span>TUT也是一个不错的解决ҎQ利用高U?/span>C++ Template功能Q必L较新的编译器才支持,比如VC6?/span>VS.NET 2002׃支持Q必?/span>VS.NET 2003以上或?/span>Intel C++ Complier 8.1以上?/span>
1?nbsp;TUT
l构框架单。添加新的测试工作量;无须注册试Q可UL性好Q因其只需两个头文Ӟ可以完成测试工作)Q便于装卸;提供接口可以扩展其输出方式等?/span>
最大的优点Q轻量Q便于装卸和可扩展其输出方式Q?/span>
~点Q断ag不是很好Q只用了一?/span>ensure()函数Q不知道对复杂的试是否支持Q输出的试l果较ؓ单?/span>
2?nbsp;Boost::test
l构框架较ؓ复杂。添加新的测试工作量也不大;提供多种试ҎQ可注册试用例Q也可不注册Q可UL性一般;装卸不易Q在控制异常、崩溃方面的能力胜过其它所有对手;拥有良好的断a功能Q大概能支持多种输出方式Q但更改输出方式不易Q支持测试套件?/span>
最大的优点Q控制异常崩溃的能力、良好的断言、输出结果较l、编写测试的Ҏ灉|Q?/span>
~点Q结构框架较为复杂,更改输出方式不易Q装怸易?/span>
3?nbsp;CXXTest
l构框架的复杂性处?/span>TUT?/span>boost::test之间。添加新的测试工作量非常;无须注册试用例Q可UL性很好;便于装卸Q控制异常、崩溃方面的能力也不错;拥有良好的断a功能Q支持多U输出方式;支持试套g?/span>
最大的优点Q?/span>~译x试方式,q且可以双击l果行立卛_位到相应的源代码Q相当吸引hQ支持多U输出,输出l果较ؓ详细Q编写测试简单;
~点Q需要用?/span>perlҎ试代码进行文法扫描,生成可执行代码,需要用?/span>makefile文gQ不是必)Q准备工作比较麻烦?/span>
http://www.gamesfromwithin.com/articles/0412/000061.htmlQ?/font>在这文章中QNoel Llopis提出了一个对C++ test framework评判的一些依据,按照Noel Llopisl出的重要性,我节略在q里?/p>
1.加入新测试最化工作?/p>
2.便于修改和移植(作者的意思是说比如RTTIQSTLQExceptionq些高Ҏ可能妨在不同的^収ͼ不同版本~译器下面的可移植性) 3.便于装配/拆卸试环境 4.对异总及崩溃很好的控制 5.好的断言功能 6.支持不同的输出方?/p>
7.支持试套g(suites) 按照q个标准QNoel Llopis对下面的test frameworkq行了评?/p>
CPPUnit 1.工作量多 2.CPPUnit能在Windows , Linux上面q行Q功能进行了很好的模块化Q但是另一斚wQCPPUnit需要RTTIQSTLQ或者异常(作者不是很肯定Q?/p>
3. 4.CPPUnit使用protectors包装试Qƈ且捕捉所有的异常Q尝试识别某些异常)QLinux下面不会捕捉pȝ异常Q但是要增加自定义的包装是很Ҏ的?/p>
5.很好Q支持一个最集合的断言语句Q包括比较QҎ?/p>
6.支持 7.支持 M评hQOverall, CppUnit is frustrating because it's almost exactly what I want, except for my most wanted feature. (CPPUnit够闷的,不过我觉得改q易用性应该可以期待) Boost.TestQ我试使用Q在VC.Net 2003下面遇到链接问题Q还没有解决Q?/p>
1.基本满 2.和CPPUnitcMQ但的是改代码的隑ֺ以及依赖Boost本n 3.避开了常规的setup/teardownl构Q可以不需要动态生成fixture 对象Q可以将fixture对象攑ֈstack里面?/p>
4.Boost.Test在这斚w过了所有的其他竞争Ҏ 5.Yes 6.大概能支持,但改变输件事情ƈ不是很容?/p>
7.支持Q?..Q这句如何理解?Yes, but with a big catchQ?/p>
Overall,Boost.Test is a library with a huge amount of potential. It has great support for exception handling and advanced assert statements. It also has other fairly unique functionality such as support for checking for infinite loops, and different levels of logging. On the other hand, it's very verbose to add new tests that are part of a suite, and it might be a bit heavy weight for game console environments. CppUnitLite(׃作者比较了一个被他改动的版本Q我不再xQ?/p>
NanoCppUnitQ这个库甚至需要你Mweb pages上面copy代码Q然后自己搞一个工E,我觉得我不太喜欢q种方式的package发布Q毕竟,我希望少操心Q所以我也不xQ?/p>
Unit++ 首先指出一个独特的Ҏ:More C++ LikeQ作者的意思是它没有用宏Q的,前面几种framework开始一个测试的时候都使用了宏Q这在许多C++ Library中是惯例Q用来简化一些代码。我们通过从基cȝ承从而创建测试包Q当然在其他framework里面本质也是q样Q但是都攑֜q后q行Q宏掩盖了具体情c?/p>
1.不好 2.一般般 3.不支?/p>
4.表现q_ 5.文档没说如何支持不同的输?/p>
6.不支持QҎ 7.支持 CxxTest 首先作者认为文档最好(很重要?Q另外作者指?CxxTest的作者Erez Volk意识到我们是在写工具帮助试C++E序Q所以不必受限于C++的特征? 1.非常?/p>
2.很好 3.支持 4.很好 5.yes 6.yes 7.yes 文章最后给Z个综qͼ是个表现好的QCPPUnit, CppUnitLite, Boost.Test, CxxTestQ作者本人喜ƢCxxTest. (?
]]>
全面介绍单元试 Q{?/span>
q是一全面介l单元测试的l典之作Q对理解单元试和Visual Unit很有帮助Q作者老纳Q收录时作了量修改
一 单元试概述
工厂在组装一台电视机之前Q会Ҏ个元仉q行试Q这Q就是单元测试?br> 其实我们每天都在做单元测试。你写了一个函敎ͼ除了极简单的外,L要执行一下,看看功能是否正常Q有时还要想办法输出些数据,如弹Z息窗口什么的Q这Q也是单元测试,老纳把这U单元测试称Z时单元测试。只q行了时单元测试的软gQ针对代码的试很不完整Q代码覆盖率要超q?0%都很困难Q未覆盖的代码可能遗留大量的l小的错误,q些错误q会互相影响Q当BUG暴露出来的时候难于调试,大幅度提高后期测试和l护成本Q也降低了开发商的竞争力。可以说Q进行充分的单元试Q是提高软g质量Q降低开发成本的必由之\?br> 对于E序员来_如果L了对自己写的代码q行单元试的习惯,不但可以写出高质量的代码Q而且q能提高~程水^?br> 要进行充分的单元试Q应专门~写试代码Qƈ与品代码隔R老纳认ؓQ比较简单的办法是ؓ产品工程建立对应的测试工E,为每个类建立对应的测试类Qؓ每个函数Q很单的除外Q徏立测试函数。首先就几个概念谈谈老纳的看法?br> 一般认为,在结构化E序时代Q单元测试所说的单元是指函数Q在当今的面向对象时代,单元试所说的单元是指cR以老纳的实跉|看,以类作ؓ试单位Q复杂度高,可操作性较差,因此仍然d以函C为单元测试的试单位Q但可以用一个测试类来组l某个类的所有测试函数。单元测试不应过分强调面向对象,因ؓ局部代码依然是l构化的。单元测试的工作量较大,单实用高效才是硬道理?br> 有一U看法是Q只试cȝ接口(公有函数)Q不试其他函数Q从面向对象角度来看Q确实有光理,但是Q测试的目的是找错ƈ最l排错,因此Q只要是包含错误的可能性较大的函数都要试Q跟函数是否U有没有关系。对于C++来说Q可以用一U简单的Ҏ区隔需试的函敎ͼ单的函数如数据读写函数的实现在头文g中编?inline函数)Q所有在源文件编写实现的函数都要q行试(构造函数和析构函数除外)?br> 什么时候测试?单元试早好Q早C么程度?XP开发理ITDDQ即试驱动开发,先编写测试代码,再进行开发。在实际的工作中Q可以不必过分强调先什么后什么,重要的是高效和感觉舒适。从老纳的经验来看,先编写品函数的框架Q然后编写测试函敎ͼ针对产品函数的功能编写测试用例,然后~写产品函数的代码,每写一个功能点都运行测试,随时补充试用例。所谓先~写产品函数的框Ӟ是指先编写函数空的实玎ͼ有返回值的随便q回一个|~译通过后再~写试代码Q这Ӟ函数名、参数表、返回类型都应该定下来了,所~写的测试代码以后需修改的可能性比较小?br> p试Q单元测试与其他试不同Q单元测试可看作是编码工作的一部分Q应该由E序员完成,也就是说Q经q了单元试的代码才是已完成的代码,提交产品代码时也要同时提交测试代码。测试部门可以作一定程度的审核?br> 关于桩代码,老纳认ؓQ单元测试应避免~写桩代码。桩代码是用来代替某些代码的代码,例如Q品函数或试函数调用了一个未~写的函敎ͼ可以~写桩函数来代替该被调用的函敎ͼ桩代码也用于实现试隔离。采用由底向上的方式q行开发,底层的代码先开发ƈ先测试,可以避免~写桩代码,q样做的好处有:减少了工作量Q测试上层函数时Q也是对下层函数的间接测试;当下层函CҎQ通过回归试可以认修改是否D上层函数产生错误?br>
二 试代码~写
多数讲述单元试的文章都是以JavaZQ本文以C++ZQ后半部分所介绍的单元测试工具也只介lC++单元试工具。下面的CZ代码的开发环境是VC6.0?br>
产品c:
class CMyClass
{
public:
int Add(int i, int j);
CMyClass();
virtual ~CMyClass();
private:
int mAge; //q龄
CString mPhase; //q龄阶段Q如"年"Q?青年"
};
建立对应的测试类CMyClassTesterQؓ了节U编q,只列出源文g的代码:
void CMyClassTester::CaseBegin()
{
//pObj是CMyClassTestercȝ成员变量Q是被测试类的对象的指针Q?br> //为求单,所有的试c都可以用pObj命名被测试对象的指针?br> pObj = new CMyClass();
}
void CMyClassTester::CaseEnd()
{
delete pObj;
}
试cȝ函数CaseBegin()和CaseEnd()建立和销毁被试对象Q每个测试用例的开头都要调用CaseBegin()Q结N要调用CaseEnd()?br>
接下来,我们建立CZ的品函敎ͼ
int CMyClass::Add(int i, int j)
{
return i+j;
}
和对应的试函数Q?br>void CMyClassTester::Add_int_int()
{
}
把参数表作ؓ函数名的一部分Q这样当出现重蝲的被试函数Ӟ试函数不会产生命名冲突。下面添加测试用例:
void CMyClassTester::Add_int_int()
{
//W一个测试用?br> CaseBegin();{ //1
int i = 0; //2
int j = 0; //3
int ret = pObj->Add(i, j); //4
ASSERT(ret == 0); //5
}CaseEnd(); //6
}
W?和第6行徏立和销毁被试对象Q所加的{}是ؓ了让每个试用例的代码有一个独立的域,以便多个试用例使用相同的变量名?br>W?和第3行是定义输入数据Q第4行是调用被测试函敎ͼq些Ҏ理解Q不作进一步解释。第5行是预期输出Q它的特Ҏ当实际输Z预期输出不同时自动报错,ASSERT是VC的断a宏,也可以用其他类似功能的宏,使用试工具q行单元试Ӟ可以使用该工具定义的断言宏?br>
CZ中的格式昑־很不z,2、3??行可以合写ؓ一行:ASSERT(pObj->Add(0, 0) == 0);但这U不z的格式却是老纳极力推荐的,因ؓ它一目了Ӟ易于建立多个试用例Qƈ且具有很好的适应性,同时Q也是极佳的代码文档QMQ老纳Q输入数据和预期输出要自成一块?br> 建立了第一个测试用例后Q应~译q运行测试,以排除语法错误,然后Q用拷?修改的办法徏立其他测试用例。由于各个测试用例之间的差别往往很小Q通常只需修改一两个数据Q拷?修改是徏立多个测试用例的最快捷办法?br>
三 试用例
下面说说试用例、输入数据及预期输出。输入数据是试用例的核心,老纳对输入数据的定义是:被测试函数所d的外部数据及q些数据的初始倹{外部数据是对于被测试函数来说的Q实际上是除了局部变量以外的其他数据Q老纳把这些数据分为几c:参数、成员变量、全局变量、IO媒体。IO媒体是指文g、数据库或其他储存或传输数据的媒体,例如Q被试函数要从文g或数据库d数据Q那么,文g或数据库中的原始数据也属于输入数据。一个函数无论多复杂Q都无非是对q几cL据的d、计和写入。预期输出是指:q回值及被测试函数所写入的外部数据的l果倹{返回值就不用说了Q被试函数q行了写操作的参?输出参数)、成员变量、全局变量、IO媒体Q它们的预期的结果值都是预期输出。一个测试用例,是讑֮输入数据Q运行被试函数Q然后判断实际输出是否符合预期。下面D一个与成员变量有关的例子:
产品函数Q?br>void CMyClass::Grow(int years)
{
mAge += years;
if(mAge < 10)
mPhase = "儿童";
else if(mAge <20)
mPhase = "年";
else if(mAge <45)
mPhase = "青年";
else if(mAge <60)
mPhase = "中年";
else
mPhase = "老年";
}
试函数中的一个测试用例:
CaseBegin();{
int years = 1;
pObj->mAge = 8;
pObj->Grow(years);
ASSERT( pObj->mAge == 9 );
ASSERT( pObj->mPhase == "儿童" );
}CaseEnd();
在输入数据中对被试cȝ成员变量mAgeq行赋|在预期输Z断言成员变量的倹{现在可以看到老纳所推荐的格式的好处了吧Q这U格式可以适应很复杂的试。在输入数据部分q可以调用其他成员函敎ͼ例如Q执行被试函数前可能需要读取文件中的数据保存到成员变量Q或需要连接数据库Q老纳把这些操作称为初始化操作。例如,上例?ASSERT( ...)之前可以加pObj->OpenFile();。ؓ了访问私有成员,可以测试类定义Z品类的友元类。例如,定义一个宏Q?br>#define UNIT_TEST(cls) friend class cls##Tester;
然后在品类声明中加一行代码:UNIT_TEST(ClassName)?br>
下面谈谈试用例设计。前面已l说了,试用例的核心是输入数据。预期输出是依据输入数据和程序功能来定的,也就是说Q对于某一E序Q输入数据确定了Q预期输Z可以确定了Q至于生?销毁被试对象和运行测试的语句Q是所有测试用例都大同异的,因此Q我们讨论测试用例时Q只讨论输入数据?br> 前面说过Q输入数据包括四c:参数、成员变量、全局变量、IO媒体Q这四类数据中,只要所试的程序需要执行读操作的,p讑֮其初始|其中Q前两类比较常用Q后两类较少用。显Ӟ把输入数据的所有可能取值都q行试Q是不可能也是无意义的,我们应该用一定的规则选择有代表性的数据作ؓ输入数据Q主要有三种Q正常输入,边界输入Q非法输入,每种输入q可以分c,也就是^常说的等L法,每类取一个数据作入数据,如果试通过Q可以肯定同cȝ其他输入也是可以通过的。下面D例说明:
正常输入
例如字符串的Trim函数Q功能是字W串前后的空格去除,那么正常的输入可以有四类Q前面有I格Q后面有I格Q前后均有空|前后均无I格?br> 边界输入
上例中空字符串可以看作是边界输入?br> 再如一个表C年龄的参数Q它的有效范围是0-100Q那么边界输入有两个Q??00?br> 非法输入
非法输入是正常取D围以外的数据Q或使代码不能完成正常功能的输入Q如上例中表C年龄的参数Q小?或大?00都是非法输入Q再如一个进行文件操作的函数Q非法输入有q么几类Q文件不存在Q目录不存在Q文件正在被其他E序打开Q权限错误?br> 如果函数使用了外部数据,则正常输入是肯定会有的,而边界输入和非法输入不是所有函数都有。一般情况下Q即使没有设计文档,考虑以上三种输入也可以找出函数的基本功能炏V实际上Q单元测试与代码~写?#8220;一体两?#8221;的关p,~码时对上述三种输入都是必须考虑的,否则代码的健壮性就会成问题?br>
四 白盒覆盖
上面所说的试数据都是针对E序的功能来设计的,是所谓的黑盒试。单元测试还需要从另一个角度来设计试数据Q即针对E序的逻辑l构来设计测试用例,是所谓的白盒试。在老纳看来Q如果黑盒测试是_充分的,那么白盒试没有必要,可惜“_充分”只是一U理想状态,例如Q真的是所有功能点都测试了吗?E序的功能点是h为的定义Q常常是不全面的Q各个输入数据之_有些l合可能会生问题,怎样保证q些l合都经q了试Q难于衡量测试的完整性是黑盒试的主要缺P而白盒测试恰恰具有易于衡量测试完整性的优点Q两者之间具有极好的互补性,例如Q完成功能测试后l计语句覆盖率,如果语句覆盖未完成,很可能是未覆盖的语句所对应的功能点未测试?br> 白盒试针对E序的逻辑l构设计试用例Q用逻辑覆盖率来衡量试的完整性。逻辑单位主要有:语句、分支、条件、条件倹{条件值组合,路径。语句覆盖就是覆盖所有的语句Q其他类推。另外还有一U判定条件覆盖,其实是分支覆盖与条g覆盖的组合,在此不作讨论。跟条g有关的覆盖就有三U,解释一下:条g覆盖是指覆盖所有的条g表达式,x有的条g表达式都臛_计算一ơ,不考虑计算l果Q条件D盖是指覆盖条件的所有可能取|x个条件的取真值和取假值都要至计一ơ;条g值组合覆盖是指覆盖所有条件取值的所有可能组合。老纳做过一些粗的研究Q发C条g直接有关的错误主要是逻辑操作W错误,例如Q||写成&&Q漏了写!什么的Q采用分支覆盖与条g覆盖的组合,基本上可以发现这些错误,另一斚wQ条件D盖与条g值组合覆盖往往需要大量的试用例Q因此,在老纳看来Q条件D盖和条g值组合覆盖的效费比偏低。老纳认ؓ效费比较高且完整性也_的测试要求是q样的:完成功能试Q完成语句覆盖、条件覆盖、分支覆盖、\径覆盖。做q单元测试的朋友恐怕会对老纳提出的测试要求给予一个字的评P晕!或者两个字的评P狂晕Q因g是不可能的要求,要达到这U测试完整性,其测试成本是不可惌的,不过Q出家h不打逛语Q老纳之所以提U测试要求,是因为利用一些工P可以在较低的成本下达到这U测试要求,后面会作进一步介l?br> 关于白盒试用例的设计,E序试领域的书c一般都有讲qͼ普通方法是dE序的逻辑l构囑֦E序程图或控制图Q根据逻辑l构图设计测试用例,q些是纯_的白盒试Q不是老纳x荐的方式。老纳所推荐的方法是Q先完成黑盒试Q然后统计白盒覆盖率Q针Ҏ覆盖的逻辑单位设计试用例覆盖它,例如Q先查是否有语句未覆盖,有的话设计测试用例覆盖它Q然后用同样Ҏ完成条g覆盖、分支覆盖和路径覆盖Q这L话,既检验了黑盒试的完整性,又避免了重复的工作,用较的旉成本辑ֈ非常高的试完整性。不q,q些工作可不是手工能完成的,必须借助于工P后面会介l可以完成这些工作的试工具?br>
五 单元试工具
现在开始介l单元测试工P老纳只介l三U,都是用于C++语言的?br> 首先是CppUnitQ这是C++单元试工具的E,免费的开源的单元试框架。由于已有一众高人写了不关于CppUnit的很好的文章Q老纳׃C了,想了解CppUnit的朋友,M下Cpluser 所作的《CppUnit试框架入门》,|址是:http://blog.csdn.net/cpluser/archive/2004/09/21/111522.aspx。该文也提供了CppUnit的下载地址?br> 然后介绍C++TestQ这是Parasoft公司的品。EC++Test是一个功能强大的自动化C/C++单元U测试工P可以自动试MC/C++函数、类Q自动生成测试用例、测试驱动函数或桩函敎ͼ在自动化的环境下极其Ҏ快速的单元的测试覆盖率辑ֈ100%?/font>q是华唐公司的网上的介l。老纳惛_些介lC++Test的文字,但发现无法超华唐公司的|页上的介绍Q所以也q点事了,想了解C++Test的朋友,讉K该公司的|站。华唐公总理C++TestQ想要购买或索取报h、试用版都可以找他们。老纳帮华唐公司做q告Q不知道会不会得点什么好处?
最后介lVisual UnitQ简UVUQ这是国产的单元试工具Q据说申请了多项专利Q拥有一批创新的技术,不过老纳只关心是不是有用和好用。E自动生成试代码 快速徏立功能测试用?E序行ؓ一目了?极高的测试完整?高效完成白盒覆盖 快速排?高效调试 详尽的测试报告]。EQ内的文字是VU开发商的网上摘录的,|址是:http://www.unitware.cn。前面所q测试要求:完成功能试Q完成语句覆盖、条件覆盖、分支覆盖、\径覆盖,用VU可以L实现Q还有一点值得一提:使用VUq能提高~码的效率,M来说Q在完成单元试的同Ӟ~码调试的时间还能大q度~短。算了,不想再讲了,老纳显摆理论、介l经验还是有兴趣的,因ؓ可以满老纳好ؓ人师的虚荣心Q但介绍工具p得烦然无味了Q毕竟工具好不好用,合不合用Q要试过才知道,q是自己d发商的网站看吧,可以下蝲演示版,q有演示课g?/font>
转自Q?/font>http://www.cnblogs.com/tester2test/archive/2006/08/04/467764.html