单元测试
2010-03-25 19:42 宝宝合凤凰 阅读(619) 评论(2) 收藏 举报
摘要
这篇文章主要阐述这样一个问题:为什么要进行烦人的单元测试?那些刚刚接触完全测试概念的开发人员常常遇到这个问题。我们这里将采用"反调论证"的方法来回答这个问题, 先提出一些反对单元测试的普遍论点, 然后我们会证明这些论点是站不住脚的。那些公开发表的文章和数据充分证实了单元测试的有效性。
IPL是一个独立的软件开发机构,成立于1979年,基地设在Bath。IPL在1988年通过了ISO9001认证,并在1991年通过TickIT认证。IPL开发并提供AdaTEST和Cantata等软件验证产品。AdaTEST和Cantata的开发遵循了这些标准的要求。
简介
在使新的产品和业务的开发过程工业化的尝试中,软件的质量和可靠性常常被看作是薄弱环节。
在最近的十年里,随着越来越多的人在开发过程中采用了设计方法论和使用CASE工具,软件质量和可靠性的问题越来越受到重视。大多数软件设计人员都接受了这方面的培训,并且在这些正规的软件设计方法的使用中取得了很多经验。
但不幸的是,软件测试并没有得到同样的重视。很多使用这些软件设计方法的开发活动并没有使软件质量和可靠性得到控制。修改最初的软件开发活动遗留的Bug一般要在软件维护费用中占到50%的比例,这是不正常的,这些Bug应该在有效的软件测试过程中被排除掉。
这篇文章主要阐述这样一个问题:为什么要进行烦人的单元测试?那些刚刚接触完全测试概念的开发人员常常遇到这个问题。我们这里将采用"反调论证"的方法来回答这个问题,先列出一些反对单元测试的普遍论点,然后我们会证明这些论点是站不住脚的。那些公开发表的文章和数据充分证实了单元测试的有效性。
什么是单元测试
单元测试是在软件开发过程中要进行的最低级别的测试活动,在单元测试活动中,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。
在一种传统的结构化编程语言中,比如C,要进行测试的单元一般是函数或子过程。在象C 这样的面向对象的语言中, 要进行测试的基本单元是类。对Ada语言来说,开发人员可以选择是在独立的过程和函数,还是在Ada包的级别上进行单元测试。单元测试的原则同样被扩展到第四代语言(4GL)的开发中,在这里基本单元被典型地划分为一个菜单或显示界面。
单元测试不仅仅是作为无错编码一种辅助手段在一次性的开发过程中使用,单元测试必须是可重复的,无论是在软件修改,或是移植到新的运行环境的过程中。因此,所有的测试都必须在整个软件系统的生命周期中进行维护。
经常与单元测试联系起来的另外一些开发活动包括代码走读(Code review),静态分析(Static analysis)和动态分析(Dynamic analysis)。静态分析就是对软件的源代码进行研读,查找错误或收集一些度量数据,并不需要对代码进行编译和执行。动态分析就是通过观察软件运行时的动作,来提供执行跟踪,时间分析,以及测试覆盖度方面的信息。
一些流行的误解
在明确了什么是单元测试以后,我们可以进行"反调论证"了。在下面的章节里,我们列出了一些反对单元测试的普遍的论点。然后用充分的理由来证明这些论点是不足取的。
它浪费了太多的时间
一旦编码完成,开发人员总是会迫切希望进行软件的集成工作,这样他们就能够看到实际的系统开始启动工作了。 这在外表上看来是一项明显的进步,而象单元测试这样的活动也许会被看作是通往这个阶段点的道路上的障碍, 推迟了对整个系统进行联调这种真正有意思的工作启动的时间。
在这种开发步骤中,真实意义上的进步被外表上的进步取代了。系统能够正常工作的可能性是很小的,更多的情况是充满了各式各样的Bug。在实践中,这样一种开发步骤常常会导致这样的结果:软件甚至无法运行。更进一步的结果是大量的时间将被花费在跟踪那些包含在独立单元里的简单的Bug上面,在个别情况下,这些Bug也许是琐碎和微不足道的,但是总的来说,他们会导致在软件集成为一个系统时增加额外的工期, 而且当这个系统投入使用时也无法确保它能够可靠运行。
在实践工作中,进行了完整计划的单元测试和编写实际的代码所花费的精力大致上是相同的。一旦完成了这些单元测试工作,很多Bug将被纠正,在确信他们手头拥有稳定可靠的部件的情况下,开发人员能够进行更高效的系统集成工作。这才是真实意义上的进步,所以说完整计划下的单元测试是对时间的更高效的利用。而调试人员的不受控和散漫的工作方式只会花费更多的时间而取得很少的好处。
使用AdaTEST和Cantata这样的支持工具可以使单元测试更加简单和有效。但这不是必须的,单元测试即使是在没有工具支持的情况下也是一项非常有意义的活动。
它仅仅是证明这些代码做了什么
这是那些没有首先为每个单元编写一个详细的规格说明而直接跳到编码阶段的开发人员提出的一条普遍的抱怨, 当编码完成以后并且面临代码测试任务的时候,他们就阅读这些代码并找出它实际上做了什么,把他们的测试工作基于已经写好的代码的基础上。当然,他们无法证明任何事情。所有的这些测试工作能够表明的事情就是编译器工作正常。是的,他们也许能够抓住(希望能够)罕见的编译器Bug,但是他们能够做的仅仅是这些。
如果他们首先写好一个详细的规格说明,测试能够以规格说明为基础。代码就能够针对它的规格说明,而不是针对自身进行测试。这样的测试仍然能够抓住编译器的Bug,同时也能找到更多的编码错误,甚至是一些规格说明中的错误。好的规格说明可以使测试的质量更高,所以最后的结论是高质量的测试需要高质量的规格说明。
在实践中会出现这样的情况: 一个开发人员要面对测试一个单元时只给出单元的代码而没有规格说明这样吃力不讨好的任务。你怎样做才会有更多的收获,而不仅仅是发现编译器的Bug?第一步是理解这个单元原本要做什么, --- 不是它实际上做了什么。 比较有效的方法是倒推出一个概要的规格说明。这个过程的主要输入条件是要阅读那些程序代码和注释, 主要针对这个单元, 及调用它和被它调用的相关代码。画出流程图是非常有帮助的,你可以用手工或使用某种工具。 可以组织对这个概要规格说明的走读(Review),以确保对这个单元的说明没有基本的错误, 有了这种最小程度的代码深层说明,就可以用它来设计单元测试了。
版权申明:本站文章均来自网络,如有侵权,请联系028-86262244-215 ,我们收到后立即删除,谢谢!
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有。
单元测试规格说明是单元初始状态的声明单元的输人,包括被这个单元读取得任何外部数据值;根据单元的功能性描述
=============================
理解单元测试主要内容
发布时间: 2010-2-26 16:00 作者: 未知 来源: 51Testing软件测试网采编
字体: 小 中 大 | 上一篇 下一篇 | 打印 | 我要投稿 | 每周一问,答贴有奖
单元测试大多数由开发人员来完成,测试人员技术背景较好或者开发系统软件时可能会安排测试人员进行单元测试,大多数进行的单元测试都是开发人员调试程序或者开发组系统联合调试的过程。
单元测试一般包括五个方面的测试:
一、模块接口测试
模块接口测试是单元测试的基础。只有在数据能正确流入、流出模块的前提下,其他测试才有意义。模块接口测试也是集成测试的重点,这里进行的测试主要是为后面打好基础。
测试接口正确与否应该考虑下列因素:
1、输入的实际参数与形式参数的个数是否相同;
2、输入的实际参数与形式参数的属性是否匹配;
3、输入的实际参数与形式参数的量纲是否一致;
4、调用其他模块时所给实际参数的个数是否与被调模块的形参个数相同;
5、调用其他模块时所给实际参数的属性是否与被调模块的形参属性匹配;
6、调用其他模块时所给实际参数的量纲是否与被调模块的形参量纲一致;
7、调用预定义函数时所用参数的个数、属性和次序是否正确;
8、是否存在与当前入口点无关的参数引用;
9、是否修改了只读型参数;
10、对全程变量的定义各模块是否一致;
11、是否把某些约束作为参数传递。
如果模块功能包括外部输入输出,还应该考虑下列因素:
1、文件属性是否正确;
2、OPEN/CLOSE语句是否正确;
3、格式说明与输入输出语句是否匹配;
4、缓冲区大小与记录长度是否匹配;
5、文件使用前是否已经打开;
6、是否处理了文件尾;
7、是否处理了输入/输出错误;
8、输出信息中是否有文字性错误。
二、局部数据结构测试
检查局部数据结构是为了保证临时存储在模块内的数据在程序执行过程中完整、正确,局部功能是整个功能运行的基础。重点是一些函数是否正确执行,内部是否运行正确。
局部数据结构往往是错误的根源,应仔细设计测试用例,力求发现下面几类错误:
1、不合适或不相容的类型说明;
2、变量无初值;
3、变量初始化或省缺值有错;
4、不正确的变量名(拼错或不正确地截断);
5、出现上溢、下溢和地址异常。
===============
为什么要编写单元测试?原因是单元测试有不少的优点,能够给我们的工作带来很大的帮助。
单元测试的优点
1.帮助开发人员编写代码,提升质量、减少bug。如果大家分析一下我们bug原因的构成,我想有会有一部分bug的原因是开发人员在编写工作代码的时候没有考虑到某些case或者边际条件。造成这种问题的原因很多,其中很重要的一个原因是我们对工作代码所要完成的功能思考不足,而编写单元测试,特别是先写单元测试再写工作代码就可以帮助开发人员思考编写的代码到底要实现哪些功能。例如实现一个简单的用户注册功能的业务类方法,用单元测试再写工作代码的方式来工作的话
开发人员就会先考虑各种场景相关,例如正常注册、用户名重复、没有满足必要的填写内容……等等,之后就会编写相关的测试用例
public Class UserSerivceTest(){
public userRegister_Ok(){
……
}
public userRegister_nameDuplicated(){
……
}
public userRegister_emailEmpty(){
……
}
}
编写单元测试代码的过程就是促使开发人员思考工作代码实现内容和逻辑的过程,之后实现工作代码的时候,开发人员思路会更清晰,实现代码的质量也会有相应的提升。
2. 提升反馈速度,减少重复工作,提高开发效率。开发人员实现某个功能或者修补了某个bug,如果有相应的单元测试支持的话,开发人员可以马上通过运行单元测试来验证之前完成的代码是否正确,而不需要反复通过发布war包、启动 jboss、通过浏览器输入数据等繁琐的步骤来验证所完成的功能。用单元测试代码来验证代码和通过发布应用以人工的方式来验证代码这两者的效率差很多,看到很多开发人员每天要反复执行N次发布脚本(antx之类的工具)真是痛苦。
3.保证你最后的代码修改不会破坏之前代码的功能。项目越做越大,代码越来越多,特别涉及到一些公用接口之类的代码或是底层的基础库,谁也不敢保证这次修改的代码不会破坏之前的功能,所以与此相关的需求会被搁置或推迟,由于不敢改进代码,代码也变得越来越难以维护,质量也越来越差。而单元测试就是解决这种问题的很好方法(不敢说最好的)。由于代码的历史功能都有相应的单元测试保证,修改了某些代码以后,通过运行相关的单元测试就可以验证出新调整的功能是否有影响到之前的功能。当然要实现到这种程度需要很大的付出,不但要能够达到比较高的测试覆盖率,而且单元测试代码的编写质量也要有保证。
4. 让代码维护更容易。由于给代码写很多单元测试,相当于给代码加上了规格说明书,开发人员通过读单元测试代码也能够帮助开发人员理解现有代码。很有opensource的项目都有相当量的单元测试代码,通过读这些测试代码会有助于理解生产源代码。
5. 有助于改进代码质量和设计。除了那些大拿们编写的代码,我相信很多易于维护、设计良好的代码都是通过不断的重构才得到的。虽然说单元测试本身不能直接改进生产代码的质量,但它为生产代码提供了"安全网",让开发人员可以勇敢地改进代码,从而让代码的clean和beautiful不再是梦想。
单元测试的缺点
1.单元测试的学习成本比较高。编写单元测试涉及的技术很多,如果只是单纯的使用 Junit或是TestNG这样的基础单元测试框架往往很难应对各种复杂的单元测试情况,所以势必要借助很多第三方的框架和技术(easymock,jmock,dbunit等等),这些框架和技术的学习还是会增加学习的成本和难度。
2.编写单元测试会增加程序员工作量。单元测试跟生产代码是一样的,并不会应为是用来测试的就有所不同,开发人员同样要面对测试代码的编写、维护等工作,也同样要面对避免重复代码等一系列问题,能否写出好的测试代码还是取决于开发人员的设计和编码能力。
3. 推广和运用单元测试需要比较大的投入。只有在每个开发人员都编写了足够的、质量好的单元测试代码,大家才能真正享受到单元测试带给我们的好处。在达到这种层度以前,还需要不少实现和资源的投入。
总结
虽然单元测试也有一些缺点和负面的效应,但跟单元测试的优点比较起来,为了克服和解决这些缺点所在的付出是值得的。
==============
代码走读的英文是Code Walkthrough。
代码走读是一个非正式的同行评审手段。在该评审中,代码被使用一些简单的测试用例进行人工执行,程序变量的状态被手工分析,以分析程序的逻辑和假设。
代码走读在形式上可以遵从同行评审的结构化的正规检视、走查、单人复审等;
人工走读时,检查单可以按照头脑风暴、亲和图、鱼骨图方法形成系统化的检查树和处理机制;
工具走读可以借助一些商用的测试工具和自己开发的辅助工具进行走读。
本文讲述的是代码走读的概念,以及什么是代码走读,还有代码走读的方法。
相关概念:因果图、测试用例、边界条件测试。
代码走读常见问题解答
(1) 代码走读都有哪些内容?
代码走读根据目的的不同,可以分为四个层次:
1、检查是否符合编程规范;
2、寻找编译器中的设计陷阱;
3、快速理解源代码,找出流程设计中的问题;
4、对原有代码的重构;
这四个层次可以按照从简单到复杂的顺序进行。
(2) 这四个层次都有什么区别和意义?
1、 检查是否符合编程规范;
编程规范融合并提炼了许多人多年开发编程语言程序积累下来的成熟经验,帮助编程者形成良好的编程风格,提高源程序的可读性和可维护性,降低出错的机会,迅速跨入业已存在的且具有相当高度的技术层次,并能够为提高代码的复用性提供积极的参考。
2、 找编译器中的设计陷阱;
术语“陷阱”的发展历史并不明确,而且它有多种定义方法。本文定义为编程和设计过程中常见的和可防止的问题,能顺利通过编译,没有任何警告和错误信息,而且计算机严格按照作者写明的代码执行,但是结果却不是作者期望的。许多IT人士都知道,现在市场上有很多新的编译器,它们可以捕获大部分程序错误,但遗憾的是,仍有许多错误是编译器不能发现的。打个比方,拼写检查程序是用来查找拼写错误的,但是,如果单词DOG被错误地写为CAT,您能指出单词CAT(实际是DOG)中的拼写错误吗?很显然,不能。因为这个单词可顺利通过拼写检查程序。
这里描述的陷阱所包括的范围广泛,从较容易的语法问题,基本设计缺陷,到完全错误的行为。利用正确的使用方法来说明这些常见的误解和误用,可以防止编程者出现类似的问题,并防止新一代程序员重复过去的错误。
3、 快速理解源代码,找出流程设计中的问题;
无论是沟通程序的操作,还是将知识存储为可执行的形式,软件的源代码都是最终的介质。我们可以将源代码编译成可执行程序,也可以阅读代码来了解程序的功能及其工作方式,还可以修改源代码来改变程序的功能。大多数编程课程和书籍都将重点放到如何从零开始编写程序上。然而,在软件系统的工作投入中,40%~70%是用在系统首次编写完整之后,这些工作一定涉及到阅读、理解、以及修改最初的代码。另外,遗留代码持续不断、不可避免的累积;对软件重用的强调;软件行业中人员的高流动性;同时,开放源代码开发工作和协同开发过程(包括外包、代码走查和极限编程)日益重要,使得代码阅读成为当今软件工程师的一项基本功能。此外,阅读实际的、编写良好的代码,可以更加深入地了解如何改造与编写重要的系统,仅仅编写小型的程序学不到这种能力。
有时,阅读代码是一件不得不去做的事,比如:为了修复、检查或改进现存的代码,都必须去阅读相关的代码。有些时候,阅读代码也许是为了了解程序是如何工作的,对于任何能够“打开盖子”的事务,作为工程技术人员,我们总是倾向于分析一下它的内部结果。您阅读代码可能是想提取可供重用的材料,或者仅仅是出于个人兴趣,将代码作为一种文献。每种原因的代码阅读都有自己的一套技术,强调不同方面的技能。
代码走读中的阅读源代码强调的是通过快速理解源代码,找出流程设计中的问题这个目的。
4、 对原有代码的重构;
重构的含义是:在不破坏可观察功能的前提下,借由搬移、提炼、打散、凝聚……,改善事务的体质、强化当前的可读性、为将来的扩充性和维护性做准备、乃至于在过程中找出潜在的“臭虫”,就成了大受欢迎的稳步前进的良方;
(3) 编程规范、设计模式和设计陷阱是什么关系?
模式是避免陷阱或从特定陷阱中恢复的一种方法;
从陷阱的角度来看,设计模式有两个重要属性。首先,他们描述了经过实践证明的成功的设计技术,而且可以用上下文相关的方法定制它们,以适应新的设计情况。其次,更加重要的是,提及特定的模式时,不仅说明了所应用的技术,而且说明了应用的原因以及结果;
当需要时,适合设计或者编码上下文的模式、惯例、编程规范,将“自然地”从自己的潜意识中冒出来,这说明正确使用了模式、惯例、编程规范的一种迹象;
识别陷阱与对条件的反射类似,一朝被蛇咬,十年怕井绳。然而与比赛和打仗一样,为了学习如何识别和避免危险情况,并不需要一定被烧伤或者被枪伤。一般情况下,必要条件就是提前警觉;
(4) 设计模式和重构是什么关系?
设计模式给我们的,不仅仅是一些问题的解决方案,更有追求完美“模型”的渴望,但是,Joshua Kerievsky在那篇著名的《模式与XP》中明白地指出:在设计前期使用模式常常导致过度过程。这是一个残酷的现实,单凭对完美的追求无法写出实用的代码,“实用”是软件压倒一切的要素。
(5) 快速理解源代码和重构是什么关系?
进行重构时,您从一个能够正常工作的系统开始做起,希望确保结束时系统能够正常工作。一套恰当的测试用来可以帮助您满足此项约束,所以重构应该从编写测试用例入手。一种类型的重构专注于修复一种已知的问题点。在此,您必须理解老的代码、设计新的实现、研究新的实现对相关其它代码造成的影响(多数情况下,新代码能够“无声无息”地完成替换)并实现更改,所以重构需要先快速理解老的代码。
(6)快速理解源代码查找缺陷和寻找设计陷阱查找缺陷有什么不同?
快速理解源代码找出的代码中的问题一般是流程设计上和软件需求满足上的特定的问题,需要读者翻很多页(发现前后的关联),而寻找设计陷阱找出的代码中的问题一般是普遍性的问题,一般不需要读者翻页,就在这一行的上下文中就可以找到。
(7)这几个层次的代码走读和单元测试是什么关系?
只有快速理解了源代码才可以完成单元测试,或者说快速理解源代码是完成单元测试的前提;
利用单元测试可以帮助更好地重构;
代码走读发现的问题比单元测试发现的更多、更快和更早;
单元测试发现不了不满足编程规范的问题
(8)代码走读都有哪些方法?
形式上可以遵从同行评审的结构化的正规检视、走查、单人复审等;
人工走读时,检查单可以按照头脑风暴、亲和图、鱼骨图方法形成系统化的检查树和处理机制;
工具走读可以借助一些商用的测试工具和自己开发的辅助工具进行走读。
(9)代码走读听起来是不错,如何才能达到效果吗?
代码走读中使用的检查单(或检查树)是很多人提炼和总结出来的结晶,市场和业界这方面的资料比较缺乏,因为多是个别大公司或个人的心血,所以很少在外面流传,自己研究和总结有点得不偿失,不如参考行业的优秀实践,所以最好接受有经验的专家的培训或向有经验的同行请教,在指导下开展推行,避免浪费自己的宝贵时间。
(10)代码走读和同行评审是什么关系?
同行评审是一种比较偏管理的方法,评审的材料可以包括文档和代码,对于代码的同行评审就是代码走读,本文讲的代码走读偏重于技术层面的方法,两者只有有效地结合才能更大地发挥它们的威力!
========================================================
单元测试说明
(Unit Test Descriptions)
1 引言
1.1 目的
本文档为XX项目的单元测试活动提供测试设计规格及测试用例规格。文档内容包括了需要测试的类、测试使用的模型、针对每个类的测试策略以及所需执行的测试用例等。
本文档的读者主要是开发经理和开发人员。
1.2范围
本文档是单元测试文档的一部分,主要用于说明单元测试设计规格,以及在设计规格指导下进行单元测试用例设计。
1.3参考文献
《软件需求规格说明(Software Requirement Specification)》
《软件设计说明(Software Design Descriptions)》
《用户界面规格说明(User Interface Specification)》
《单元测试计划(Unit Test Plan)》
1.4术语
无。
2 被测对象
XX Code Base Line版本
3 被测函数
本单元测试需要测试的函数如表1所示。
表1 被测函数
|
方法标识符 |
方法名 |
代码行(LOC) |
复杂度(VG) |
|
|
|
|
|
|
... |
... |
|
|
4 测试总体设计方法
为测试类与被测试类创建单独的目录,类中使用相同的Package名,以便代码分离和查找。测试类名是在被测试类名前加上“Test”,以区分测试类和被测试类。测试类中包含测试驱动代码、桩代码和测试用例代码。
在是否使用桩代码的策略上,对逻辑简单的函数采用直接调用方法。对于打桩了的函数,在测试时不考虑其内部逻辑是如何处理的,仅检测其接受的输入参数是否合法,并根据用例需要设定其返回值。
回归测试的时候,可利用JUnit的测试套件、参数化测试功能进行批量测试。
5 测试模型设计
5.1测试组网图
5.2操作流程
6 测试规格
6.1 XX测试规格
1 XX测试设计规格
(1)设计标识符
(2)被测特性
l 输入参数合法,并且读者为合法用户,登记借书记录。
l 输入参数合法,但读者为不合法用户,反馈错误信息。
l 输入读者编号有误,反馈错误信息。
l 输入书目编号有误,反馈错误信息。
(3)测试方法
(对测试总体设计方法进行具体化,略)
(4)测试项标识符
测试项标识符见表2。
表2 测试项标识符
|
测试项标识符 |
测试项描述 |
优先级 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(5)测试通过/失败标准
优先级高和中的测试用例中,只要出现任何一个失败,则测试失败。
2 XX测试用例规格
XX测试用例规格参考表3-表6。
表3 测试输入参数合法,并且读者为合法用户情况
|
测试项标识符 |
| ||
|
优先级 |
| ||
|
测试项描述 |
| ||
|
前置条件 |
| ||
|
测试用例序号 |
输入 |
执行步骤 |
预期结果 |
|
001 |
|
|
|
|
002 |
|
|
|
|
003 |
|
|
|
(表3-表6略)
6.2 XX测试规格
...
7 审批
测试规格提交人签字: 日期:
开发经理签字: 日期:
浙公网安备 33010602011771号