qileilove

blog已经转移至github,大家请访问 http://qaseven.github.io/

做了7年手工测试迷茫了怎么办?——给你的建议


在新浪微博上有个朋友问到这个问题:“有多少是测试年限在7年以上但还仍然在做大量手工测试的。。。。,请朱老师给指导一条明路”,限于微博140的限制,我还是在blog中来分享一些我的观点:

   手工测试工作并不比自动化测试设计,工具使用或开发简单很多。你可以每年反思总结自己在测试分析的方法及能力上——减少测试对象遗漏,测试设计的方法及能力上——测试深度和去冗余,取得了什么进步,这就是你每年的努力提升方向。 自动化和工具毕业生都可以快速掌握,但如何设计一套漏测少,冗余少的测试用例框架则需要你丰富的手工测试经验为基础;如何设计一套能缩短研发进度保障测试质量的测试策略需要丰富的手工测试经验为基础。我03年就开始开发自动化测试框架(可以让功能测试人 员只需要会脚本的if  for就可以完成自己的自动化测试脚本),一行一行写的,相比国内目前大多数搞自动化测试的人算走得早的吧,07年时还参与给某大型IT厂商(测试有 400人当时)进行自动化测试咨询(自动化测试框架以外的自动化测试相关经验)。08年底出版的《软件测试精要》一书中所提到的一些自动化测试的部分经验在2010年的时候还被华为内部某些专职自动化测试工作人员提炼总结和传播,内部下载量上百。我从03年唯自动化测试工作论,到后面慢慢淡化,逐渐从事了其他测 试类型的工作后我发现在测试领域自动化测试的重要性没我想得那么高,也没那么关键后,从08年起我完全离开了自动化测试领域,并发现了更多有价值更有意义 更有挑战性的测试技术创新专题。如果关注我的blog,我的新浪微博会发现这几年我分享的测试技术和经验都是非自动化的,现在我还在路上。要知道测试的根 不是自动化,而是质量,在质量这一块手工测试经验人员是大有可为,也只有依赖你来大有可为,自动化测试的同事搞不定质量,也无法全局角度整体来提升效率, 自动化测试提升的效率从整个研发周期和测试周期来看,只能算是局部优化。搞好测试策略,搞好用例冗余也都能整体提升测试效率。

posted @ 2011-10-10 10:08 顺其自然EVO| 编辑 收藏

测试用例设计心得

  一个好的测试用例是每个人都能执行的测试用例,不管你是否是测试人员,不管你是否了解整个软件的工作流程,你都能顺利的执行完测试用例,并对这个测试用例覆盖到的功能点有了大概的了解。

  好的测试用例的设计相当了软件开发中 的详细概要设计,要写出好的测试用例首先要对所测试的软件很熟悉,熟悉软件的每个功能点和系统的整个业务流程。其次,对整个测试用例有个好的规划,理清主 线,功能点的在哪个地方被覆盖都是需要考虑的。最后,需要良好的心态,写测试用例是个繁琐的过程,测试用例不是随随便便就能写出来的,好的测试用例更需要 你在写的过程中不断去理清思路,并把每个功能点都恰当的写进去。

  测试用例的规划:

   用例的规划非常的重要,它决定整个测试用例的思路、风格、覆盖率。即对整个测试用例的成败都有直接的响。对测试用例的规划我个人总结出两条思路:一条是 用例的线性规划,另一条是功能点覆盖型。这两条思路的侧重点各不相同,各有优缺点。线性的测试用例的要点是在理清每一条思路,即以业务线和流程线为主,每 一条线都是一个流程,然后把功能点穿插到每条线里去。把每条业务流程比作竖线,功能线比作横线,那么功能点就是横线和竖线的节点,这样整个用例就是一张大 网,我们可以随时向网中添加横线或竖线,使得覆盖率不断增加,“漏网之鱼”越来越小。

  另一种思路是功能点覆盖型。在设计之前把要整套软 件的功能点理清楚,这当然是非常的难的。但我们可以参考系统设计的功能流程图,软件的需求来进行分析和提取。还有一点就是测试人员的经验来完善所需要的功 能点。这种思路的重点是把每个功能点都要在设计中体现出来,以功能点覆盖为主,不管工作的业务流程。也就是说是按照各个功能模块进行划分的,分模块进行用 例的设计。

  两种思路相辅相承,各有各的优点。在实际的执行过程中,有时以业务流程来编写比较容易,有时以功能模块编写比较容易。一个是以线为主,一个是以块为主。

  测试用例的设计:

   规划好测试用例的整体思路之后,就是测试用例的具体设计设计了。用例的设计的格式主要由测试环境,准备数据,前置条件,用例ID,预期输入值,期望输出 结果,测试执行结果和优先级等几个部分组成。其余的还有一些统计页,打印输出的模板等。个人认为用excel设计比较简便,excel可以有多个页面,一 个页面为统计测试结果和用例维护,一个为测试用例的主页面,还有一个页面可以放一些打印后的模板。这样的设计有利于用例的维护。

  用例的预期输入值和操作步骤是用例设计最重要的部分。设计好这两个部分对经后测试用例的执行至关重要,特别是操作步骤的描述,要描述清楚每一步的操作步骤,这样才能让测试的执行者准确操作,不会产生歧义。用例所写的每一句话都应该清晰的,没有歧义的,否则就会出现用例维护时,其他测试人员看不懂你写的是什么,测试用例执行的时候,看着很费力,达不到文中刚开始的要求。

  测试用例的维护:

   每个测试用例都要在经后执行的过程中去维护修改,使得测试用例的覆盖率不断提高。特别的测试用例的第一个版本时,需要维护的量是非常大的。我们可以边测 试边修改测试用例,也可以根据需求添加测试用例。每维护一次测试用例,就必修记录下你修改的内容,以便于经后的阅读和别人的维护。

  以上是我近期对于测试用例设计的理解,也是我近期工作的一个总结和体会,测试用例设计是一门高深的技术,也是软件测试的重要组成部分,我们需要经验来不断提升用例的质量,设计出好的测试用例。

posted @ 2011-10-10 10:06 顺其自然EVO| 编辑 收藏

浅谈功能测试用例

浅谈浅谈,各位都是测试用例设计方面的高手,本篇只是抛砖引玉,分享下我在测试过程中对测试用例设计和执行的一些感悟,也希望大家能有更好的观点分享。

   首先是测试用例数据的来源,测试用例中的数据来源于需求,规范的需求人员会将用户的准确信息汇总传达给项目组的开发和测试人员(当然需求不准确是另一回 事),测试人员需要验证的是开发的实现是否符合需求的预定目标。在项目开始的时候,开发人员着手设计框架和编码,我们测试人员则排计划和准备测试用例。刚 开始的时候觉得没有需求文档一切行动就像失去目标一样。有时需求文档确实跟不上进度,测试人员是不是就不用工作等 着呢?完全不是。其实测试人员获取该产品的详细信息有很多渠道,文档只是一种其中一种文字化、实例化的方式,比较规范。测试人员完全可以和开发、团队 Leader沟通、交流,将自己对这个系统的含糊不清的地方一一明确,这样既可以弥补需求文档不足带来的不便,也加深测试人员对该系统的理解。对于需求文 档有遗漏的地方,测试人员可以通过需求评审(由客户、需求、开发、测试一起讨论)进一步明确需求。

  其次是编写用例了。个人编写用例的习 惯是用例能够覆盖这个系统的功能点即可。用例设计的元素一般由ID、名称、所属功能模块、测试前置条件、输入数据、执行步骤、期望结果和备注组成,当然为 了规范执行,一般情况下还会加上执行人、执行时间和执行结果。譬如设计一个创建对象或者子节点的用例,个人在测试步骤中只会写“正常登陆系统后,点击创建 对象功能,输入对象名称,提交保存。”期望结果就是对象能够准确无误的创建。这样一看,好像少了点东西,对,边界值检查(包括空值检查、最大值和超过最大 值检查以及特殊字符检查甚至同名检查)。个人觉得边界值检查的方法确实好,能够发现很多缺陷(前人总结的真理)。不过经过权衡后,我还是没有将这些譬如 “输入超过20个字符检查”、“不输入任何值检查”、“输入相同名称检查”等字样写在用例里。为什么?

  声明一下,我并不是反对用例写的 很详细,我只是觉得上述内容填写(又叫过度设计)对测试用例来说没有必要,有点本末倒置。一来简介版的用例可以很小,很方便维护,同时后续需求变更后很容 易修改甚至不用修改;二来给用例执行者最大的自由(我不反对将编写该用例时自己想到的闪光点写在里面),对于熟练的测试工程师来说,看到输入框的第一反应 就是不输会怎么样,超过了会怎么样,不满足你的要求你会怎么样,可能是自然反应吧(我就是这样,已经掉进这个沟里了),对于新手,我可能会加一行“注意边 界值”,可能做我的新手可能比较“倒霉”哈,我一不会告诉你用例设计的方法,二不会告诉你用例执行思路,前者我认为是有志干这行的必修课,必须自己主动掌 握,我仅能带你入门而已,至于后者则靠自己不断测试不断总结,每个人都不一样,找到那个最适合自己的。还有一个不好的地方就是太详细的用例给人的感觉就是 我不需要思考了,反正设计者已经想得很详细了,导致编写用例的时候绞尽脑汁,执行的时候完全可以被程序替代了。那样也很难发现这个系统的隐藏的缺陷了。当 然对于在设计阶段就想的很详细的用例来说,不会因为测试人员的疏忽而导致潜在缺陷被遗漏,毕竟用例规范了测试人员的行为。

  用例编写过程中不明白的问题可以先记录下来,在用例编写完成后,将所有不明白的地方汇总,提交需求确认,待确认后补充对应的内容。

  测试人员的工作是规范别人(需求、开发)的工作,我们自己的工作也必须规范,必须接受项目组其他人员的检查,用例评审就是一个很好的方式,需求、开发和测试在一起讨论达成最后的结果。测试人员负责修补其中不满足的地方。

   接下来就是执行用例的习惯了,个人喜欢在执行之前通读该功能模块的所有用例,然后统计下需要测试的功能点,对执行的顺序初步排序,这样可以能提高效率, 不至于顾此失彼。譬如有6个用例,分别是对对象的增、改、删和对对象关系的增、改、删,编写用例时的一般按照这个顺序下来,执行的时候如果这样的话那明显 增加了工作量,改成对象的增、改完成后执行对象的关系,关系执行完成后删除对象。既完整的执行了用例、测试了功能,又能做到效率最大化。

   记得一位资深的工程师说过:在一个产品的迭代两轮过后,不加入新功能的情况下,仅凭用例是很难发现问题的。而实际过程中也确实如此,正是有鉴于此,个人 比较倾向测试用例只要设计的能够完整覆盖系统的功能点即可,至于新手的执行的问题,测试用例是为了规范测试人员的工作,将无限的测试工作有限化,并且方便 管理和复用,好像并不具备新手入门手册这个功能(可以通过其他方式解决,不必在测试用例中过分迁就他们)。

  权且一家之言,不对之处欢迎讨论。

posted @ 2011-10-10 10:00 顺其自然EVO| 编辑 收藏

浅说测试用例----给测试新手的

 在此之前我搜集一些关于测试用例的知识,后来在我们的QQ群里专门定了一期讨论,来探讨测试用例,毕竟这是一个很大的话题,很难做到面面俱到,但我会尽量全面,用通俗的语言来说测试用例。

-------------------------------------------------------------------------------------------------------------------------

  注:我们这里要说的测试用例指功能测试用例。

  一、什么是测试用例?

  测试用例是为某个特殊目标而编制的一组测试输入、执行条件以及预期结果,以便测试某个程序路径或核实是否满足某个特定需求。

  通俗的讲:就是把我们测试系统的操作步骤用按照一定的格式用文字描述出来。

  二、写测试用例有什么好处?

  理清思路,避免遗漏

  这里是我们认为最重要的一点,假如我们测试的项目大而复杂,我们可以把项目功能细分,根据每一个功能通过编写用例的方式来整理我们测试系统的思路,避免遗漏掉要测试的功能点。

  跟踪测试进展

  通过编写测试用例,执行测试用例,我们可以很清楚的知道我们的测试进度。

  历史参考

  在我们所做的项目中,也许会有很多功能是相同或相近的,我们对这类功能设计了测试用例,便于以后我们遇到类似功能的时候可以做参考依据。

  重复性

  我们测试一个系统不是一个人测一遍就算测完的,需要多人反复的进行测试,那么我们就需要测试用例来规范和指导我们的测试行为。

  告诉领导,这事俺干过,不然别人怎么知道你测没测,测的全面不全面,拿测试用例给他们看呗!俺就是照着这个干活,呵呵!

  三、测试用例的方法

  好吧,咱知道啥是测试用例了,也是知道为什么要写测试用例了,那到底应该怎么写?无从下手啊。我们在写测试用例之前,先学习几种方法,它是我们写测试用例的指导思想。

  1、等价类划分

   在某个输入域的子集合,在该子集合中,各个输入数据对于揭露程序中的错误都是等价的。假如有一个输入框要求输入1-10000个数,我们不可能用每一个 数去试,我们输入5 和输入6去验证和揭露输入框的错误可以看做是等价的。那么这个时候我们就可以随机的抽取一些数据来进行验证。如:10 、99、7777......

  等价类分:有效等价类和无效等价类

  输入框要求输入1-10000的数

  有效等价类:可以输入1-10000之间的数来验证,如:2、5、99、8495......

  无效等价类:可以输入1-10000之外的任意字符验证,如:20000、字母、下划线、特殊符号、空格、回车.....

  2、边界值

  边界值是对等价类的补充,测试工作经验告诉我们,大量的错误是出在输入输出的边界价上。我们还拿上面的例子,一个输入框要求输入1-10000之间的数。我们要测它有没有超出这个范围,如:0、-1、-2、1000、10001.....等等,来判定是否超出了我们的范围。

 3、因果图

  因果图方法最终生成的就是判定表,它适合于检查程序输入条件的各种组合情况。举个例子:原因:A=0,B=0,结果我就可以判定:A=B。确切 的说他是一种因果关系思想。它会无形中指导这我们的测试。当然了,我们为了以免遗漏,可以把系统中的因果关系用图画出。不过系统大而复杂的话就是个体力活 了。呵呵。

  4、错误推测法

  基于经验和直觉推测出系统可能存在的错误,从而有针对性的设计测试用例的方法。

  5、其它

  设计测试用例的方法有很多,我们常用就上面几种,其它的方法还有:状态迁移图、流程分析法、正交验证法等等。

  四、测试用例的格式与要素

  一个测试用例应该包括:编号,标题,测试场景,测试步骤,预期结果。

  当然还可加入一些它选项,如:优先级、测试阶段....

  注:上面的格式取自《微软的软件测试之道》,它并不一定适合你,我只是让大家对测试格式有个了解。

  关于测试用例的存放管理:

  1、项目管理系统自带的用例管理,一般用例会与项目挂钩,有固定的格式,搜索、修改等功能,使用起来非常方便。如:禅道项目管理、QC、bugfree 等等都带的有用例管理功能。

  2、通过world\Excel文档形式管理,这样的好处就是自己定义测试用例的格式。

  -----------------------测试用例例子--------------------------------------------------------

  基础知识了解的差不多了,下面来看一个具体的测试用例。我们会有更深刻的认识。

  注:这不是一个完整的测试用例,格式也不是固定必须这样的,你们可以根据自己的需求编写设计测试用例。

  ------------------------------------我们还需要知道的,关于测试用例的-------------------------------

  一、我们在什么时候可以设计测试用例?

  当根据客户的需求整理出项目需求分析文档时,我们就可以根据需求文档来编写测试用例了。但是,一般我们(国内大多小公司)项目需求文档都非常“简陋”,所以,很难根据需求文档设计测试用例。

  我们只有等到项目开发人员把项目开发出来,给我们系统文档、部署环境、数据库结构(如果系统牵涉到数据库的话),我们根绝这些文档来设计测试用例。

  二、测试用例的评审与更新

  我们设计的测试用例设计完成之后,是否完整?是否符合系统?符合客户要求?对用例做一个评审是必不可少。关于评审的方式,不同的公司有不同的流程。

  我们编写的测试用例也不是经过评审之后就不变了,随着需求的变更、功能的改进,测试用例当然也需要更新和变动。

  三、什么情况下不适合写测试用例

  文件时间

  如果一个功能我很快就测试完了,而且只需要测试一遍,但我们设计测试用例时却比较麻烦,花时间也长。这个时候就没必要编写测试用例了。

  需求变动大且频繁

  需求的功能变动非常频繁,而且变动很大,之前编写的测试用例根本没法使用,必须要重新编写,这个时候也没必要去设计测试用例了。

  项目时间不允许

  这一项是不太厚道的做法,如果不是急需交付客户的话,尽量不要这样做;当然了,如果只是给客户展示或试用,可以在之后进行补充和完善测试用例。

  不要编写不完整或别人看不懂的测试用例,那样就没有意义了。

  ==========================================个人闲聊内容,欢迎指正===================================

  四、停止软件测试的标准。

  语句覆盖最低不能小于80%,测试需求覆盖率达到100%,测试用例覆盖率达到100%,一、二级缺陷修复率达到100%,三、四级修复率达到80%。

  (上面一句是再网上找的,不是标准,只是个参考)

  bug等级:

  一级:非常严重的bug
  二级:严重的bug
  三级:一般性的bug
  四级:建议性问题

  五、关于探索性测试

  完全的执行测试用例时一件非常枯燥的事情,个人在执行测试用例时会做一些,其它的非常规性的操作,看系统是否会有相应的处理和提示。我的一部分bug就是再这种非常规操作下发现的。

  当然了真正的探索性测试需要对产品的深入了解,以及软件开发技术有一定的深度和宽度。姑且把我们的探索性测试看成是瞎捣鼓吧!呵呵。

  六、交叉测试

  有木有发现,当我们第一遍测试系统时,会非常认真,但要我们测试第二遍时,我们不愿意像第一次那样认真的去测了,这不能说明我们不负责,而是每个人都有的心理现象。这个时候,我们可以和其它测试人员交换功能来测试,提高效率,而且更容易发现问题。

  七、测试的目的

  1、我们让它做的它必须会做。

  2、我们不让它做的它必须不会做。

  可能你会发现有附加功能的时候,就是客户没有要求,我们加了这样的功能,可能加了这点功能系统看上去会更好。这时怎么办?算问题么?

  作为开发人员,中规中矩的做东西最好,如果真的有非常好的功能要加的话,需要和客户沟通,然后写到需求里。毕竟多一点功能多一点风险。呵呵

  作为测试人员,凡是不符合需求文档的都需要当问题点提出。责任分明,以免后续麻烦。

  ----------------------------------------------------

  修改:

  1、测试用例的格式的要素,去掉“实际结果”

  2、关于测试方法“等价类划分”的解释。

  谢谢“zdd”朋友的纠正。:)



posted @ 2011-10-10 09:50 顺其自然EVO| 编辑 收藏

各国测试用列分析

  作为黑盒测试的一个重要阶段,功能测试毋庸置疑是不可缺失的。功能测试的相关话题很多,无论是测试的形式,例如手动测试和自动化测试,还是测试方法,例如数据驱动和关键字驱动,都有大量的研究文章。我这篇博文里却打算从国别不同的角度来讨论一下功能测试的差异,原创文章可能有一些谬误的地方,请读者指摘。

  日式循规蹈矩

  日本人给世界其他民 族的印象是做事认真严谨,对待问题一丝不苟,犯了错误按严重程度该下跪的下跪,该剖腹的剖腹。他们的这种一贯行事方式也带到了软件行业,而软件行业的摩尔 定律,技术的日新月异,代码、框架的多变,都似乎与他们的性格格格不入。日本制造的东西一向以坚固耐用著称,给我映像最深的一个细节是,在日本工作的时候,发现他们的垃圾袋居然都坚固无比,用来逛超市买东西拎重物也是屡用不坏。然而,对于遵从摩尔定律的计算机行业来说,投入大量的精力来尽可能的发现bug以及解决问题是否很值,这真的是有待商榷的问题。

  但,话虽如此,日企采用的测试用例的设计方法还是非常值得我们学习的,这其中又首先要提一下要因分析法(网上有些说法把鱼骨图等同于要因分析法,我并不赞同此说,后面会有详述)。

  要因分析法的精髓在于,以产品的某一特性为因子,以这个特性的不同表现(根据等价类划分、边界值分析等方法)为状态,一一列举后,采用二维组合的方式来确定测试用例。

  下面我举一个简单的例子“我打算从南京去北京”来说明。

表1

   这即是一张简单的要因表,值得注意的是,因子和状态的确定是必须规定范围的,而这个范围在这个例子中则为“正常人的思考”范围。譬如说,“交通方式”我 没有写“步行”,因为这不符合常人从南京去北京的思考方式,当然有人为了挑战吉尼斯纪录,这里非要采用步行方式从南京前往北京,那么状态里添加这项显然是 可以的。

  此外,因子和状态的另一个隐性的确定方针为详细度,这个度如何把握,我们可以从下表来理解:

表2

  表2将表1的“交通方式”进一步细化,此时状态的选择将进一步增多。如果要求详细度更加提高,例如因子为“动车”,那么可以加上状态为“一等座”和“二等座”,这种组合很灵活,取决于我所需的详细级别。

  要因确定之后,便是组合,以表1所列要因为例,二维组合有下列共18种:

飞机-晴-白天,飞机-雨-白天,飞机-雪-白天,飞机-晴-夜晚,飞机-雨-夜晚,飞机-雪-夜晚
火车-晴-白天,火车-雨-白天,火车-雪-白天,火车-晴-夜晚,火车-雨-夜晚,火车-雪-夜晚
汽车-晴-白天,汽车-雨-白天,汽车-雪-白天,汽车-晴-夜晚,汽车-雨-夜晚,汽车-雪-夜晚

  对于表2来说,二维组合则有2*4*2*3*2=96种,貌似有点多,当然你想分析的越详细,那么组合数量自然会相应的增加。

   回到表1得出的18种用例,假如我的通过条件是从南京到北京的时间越短越好,在实际的外界环境下(例如晴天,预期花费有限额),这18个用例中,就会出 现有的测试通过,有的测试失败的情况了。在不同的实际外界环境下,测试通过的情况还会发生变化(例如下雪天,飞机会发生班次延误)。

  要因分析法的好处在于“事无巨细,滴水不漏”,坏处在于过程“繁琐冗长,枯燥乏味”。即使如此细致的要因分析,依然存在一定的用例遗漏的风险, 这个风险来自于对因子和状态潜在的考虑不周。随着详细级别要求或者系统复杂度的提高,使用要因分析法组合出详尽的测试方案则成为了QA的一种折磨,每一个 QA遇到复杂的测试对象,都将成为酷刑下折翼的天使,欲哭无泪的在心中默默呐喊“坑爹啊”(因此要对要因法做一定程度的改良,如何改良,后文将详述)。

  前文伏笔“要因分析法不是鱼骨分析法”,鱼骨分析法的另一个更加正式的书面称呼是“根本原因分析法”(日本管理大师石川馨先生发展出来的,故又 名石川图)。根本原因分析法同样有着折磨常人的地方(题外话:为什么日本人使用的方法总是那么坑爹?),它要求分析者们集中精力寻求发生问题的任何一种可 能性(头脑风暴),将这些可能性绘制在鱼骨图上,再寻求所有可能性的根源,其本质上不是一种用于设计测试方案的方法(仅仅用于追溯问题,例如发现bug后 追溯引起bug的根源)。关于根本原因分析法的讨论,由于和本文的重点有偏离,因此不做进一步描述,题外话就此打住。

  欧美式头脑风暴

  众所周知,欧美企业的风格是强调人性和自由,对于测试来说,自然不可能采纳日式那种条条框框的做法。测试方案设计的基本方法和准则,例如边界值 分析、等价类划分、因果图等,被QA们牢牢的记在心中,功能测试方案设计时,根据需求分析或用户手册,众人在一起集中进行头脑风暴,此时包括RD也将参与 进来,对于测试合理或者不合理的地方提出建议。因此,方案设计的时候,更强调的是经验和阅历以及对需求的关注程度。测试方案设计是有偏重的,对于一些重要 的feature强烈关注,用例也将根据feature的重要性而详略有别。

  头脑风暴式的测试用例设计的辅助工具往往以思维导图为主,还是以“我打算从南京去北京”为例,其中一种思维导图设计如下:

思维导图的灵活性很高,因此设计出的导图每次都会有所不同,跟随着与会者偏重点的不同而产生不同的设计方案。

  好比欧洲的菜肴大多以整块烹制为主,而中国人的菜肴则基本上都是切碎的。这是因为受限于传统使用的餐具。而测试方案的设计方法也同样受到西方和 东方文化差异的影响,例如Agile首先在欧美出现并迅速得到推广和应用,而Agile绝对不会首先出现于日本。对于头脑风暴式的测试方案设计,这颇有 Agile的风格,项目参与者进行充分的思想交流,QA们将每一个闪现于头脑的想法都记录在案,同时根据别的QA和RD的建议来不断地修正或弥补方案,直 到完成设计。

  这种设计方法的好处是拥有很好的灵活性,且可以避免精力被耗费到旁枝末节上。用例可以是很多步骤组合起来的(表现为思维导图的层次较多),通过 一个用例测到多个feature,这在进行具体的自动化测试代码编写时可以节省大量劳动力。由于交流足够充分,某些不易被想到的测试用例也可以被挖掘出来 (好比绘制清晰的思维导图)。然而,这种方法的缺点也是显而易见的,某些测试用例有可能在头脑风暴中被忽视或遗忘,且受限于人的思维的不严密性,未设计在 案的测试用例,往往也没有人会关注到“为什么这些测试用例不用测”这个问题。

  欧美与日式的比较

  其实从上述的两个篇章,我已经阐述了二者之间的差异。日式苦行僧般的要因分析法,几乎可以遍历穷举所有可能的组合方式(除非因子有遗漏),设计 完毕后,到了具体测试实施阶段,无论是手动测试还是自动化测试,对于QA来说,都是一个比拼耐力的考验,测试用例数动辄过千,大量的测试用例之间甚至只有 细微的差别。QA将完全体验不到创作的乐趣,转而成为一名不折不扣的体力劳动者。一个测试对象的测试周期也被大大拉长,所需的人月数也很多。完成这些繁琐 的工作之后,测试对象将趋于完美,细微的bug也将被找出并修正。此时不排除测试对象可能已经是一个落后的甚至被淘汰的产品了。当然,这对于用户忠诚度极 高的日本人来说,这不算什么问题,他们不厌其烦的强调产品品质,但是对于这颗星球上的其他民族来说,大多宁可忍受一些小bug,也不愿使用一个过期的技术 落后的产品。

  对于欧美式的测试设计,显然比较契合当前的飞速发展的计算机业,但产品中留下的bug数量往往也会比日式测试法多的多。这尤其表现在产品的一些局部的、次要的功能上,这些功能往往将成为bug集中营。

  两者融合的思考

  欧美与日本,两者采用的方法各有长处。一者的缺点往往恰是另一者的长处。那么该如何从两者中取长补短,去粗取精以至互相融合呢?这也是我在工作中一直不断思考的一个问题。

  我认为有一种可行的解决方案可以按照如下的做法进行:

  1、列出要因表,因子和状态的列举方式可以采用思维导图进行

  2、根据deadline来划分测试的详略程度,制订测试计划

  3、头脑风暴,将要因表的二维组合放在参与者的头脑中进行

  4、在列举测试用例的同时,对不测的用例也要追究一下不测的原因

  5、归纳测试用例之间的共性,对于差别较小的测试用例,要考虑如何整合到一起,对于可以串行的用例,要考虑是否可以合并为一个多步骤的用例

  通过以上5个步骤,我想既可以避免要因分析法的要因组合阶段带来的让人抓狂的繁琐和用例数量过多的缺陷,又可以避免头脑风暴带来的某些用例的考虑不周。

  是否还有其他更好的取长补短的方法呢?这个问题将依然萦绕在我的日常测试工作中,吾将上下求索,并期待您对本文的看法能够给予我茅塞顿开的启迪。


posted @ 2011-10-10 09:35 顺其自然EVO| 编辑 收藏

 第二章 单元测试的基本概念和核心技法

  第二章 单元测试的基本概念和核心技法

  2.1 良好的单元测试——定义

  我们已经了解了程序员需要单元测试,下面我们来给单元测试作一个完整的定义:

  ● 定义: 单元测试是一段自动执行的代码,它调用被测类或被测方法,然后验证关于被测类或被测方法逻辑行为的假设确实成立。单元测试几乎总是用单元测试框架(unit testing framework)来写就的,单元测试是易于写就、执行快速、完全自动化、值得依赖、易于阅读并易于维护的。

  这个定义有点长,但是它却包含了大量重要信息:

  ● 单元测试的测试重点是被测类或被测方法的逻辑行为。所谓“逻辑行为”,指的是含有诸如判断、循环、选择、计算或其他决策过程的代码。

  ● 单元测试框架是辅助程序员写就单元测试的利器。

  ● 单元测试的代码本身,同被测代码一样,也应该是值得依赖、易于阅读并易于维护的。

  有了单元测试的定义,我们来看看有关单元测试的几个基本概念,这些基本概念会在后面的章节中反复出现。

  ● 被测类(Class Under Test)和被测方法(Method Under Test):顾名思义,就是测试代码所操练的类或方法。

  ● 测试类(Test Fixture)和测试方法(Test Method):负责操练被测类和被测方法。Test Method一般都是Test Fixture的成员函数。

  ● 测试运行器(Test Runner):负责自动执行测试类中的测试方法。Test Runner可以是命令行界面的,也可以是GUI的。

  2.2 进行单元测试的核心技术和核心手法

  2.2.1 核心技术

  前面已经讲到,单元测试所针对的目标是“单个”类或“单个”方法(这里的“方法”指C++中的自由函数)。这表明我们在单元测试中要做的主要任务是创建出被测类的对象,并调用该对象的被测方法。但是我们都知道,一个类几乎不可能完全不依赖于其他类。这种类之间的依赖会导致我们无法顺利地将一个被测类纳入单元测试覆盖这下,因为单元测试需要的是对被测类这一个类的测试,而不是同时测试被测类和它的合作者类。

  因此,我们在把ClassUnderTest纳入单元测试时,也就必须先把ClassUnderTest与CollaboratorClass之间的依赖“打破”,这个过程称为“解依赖”(dependency-breaking)。解依赖就是进行单元测试的核心技术。解依赖的目标是希望能把不可控的CollaboratorClass替换成由我们控制的伪合作者类(FakeCollaboratorClass),并使被测类能方便地选择是依赖于真实的合作者类还是伪合作者类。对于产品代码,被测类依赖的是真实的合作者类,而在单元测试中,被测类依赖的是由我们控制的伪合作者。

  在单元测试代码中使用可控的FakeCollaboratorClass,这给我们带来了两个便利:

  ● 我们可以通过FakeCollaboratorClass向ClassUnderTest返回任何我们指定的结果。

  ● 我们可以通过FakeCollaboratorClass来感知ClassUnderTest所做的动作。

  这实际上就是FakeCollaboratorClass的两种表现形式:

  ● Stub:用于向ClassUnderTest返回我们指定的结果。

  ● Mock:用于感知ClassUnderTest的行为。

  我们明白了“解依赖”是单元测试的核心技术。那么具体怎样实现解依赖呢?下面我们就来介绍4种相关的核心手法,其中前2种与CollaboratorClass有关,后2种与ClassUnderTest有关,这4种手法对于解决绝大多数的解依赖问题都适用。

 2.2.2 “接口提取”和“Virtual and Override”

  “接口提取”手法是对CollaboratorClass提取出一个抽象接口CollaboratorService,然后让CollaboratorClass和FakeCollaboratorClass都去实现这个接口,而ClassUnderTest则由直接依赖CollaboratorClass转向依赖于抽象接口CollaboratorService, 如下图所示。

  实际上,“接口提取”手法是一种非常好的手法,它使得我们的代码遵循“依赖抽象原则”,遵循这个原则的软件具有较好的灵活性,这是具有可测试性的软件也具有较好的设计的一个佐证。

  而“Virtual and Override”手法则是使CollaboratorClass中的被依赖方法成为virtual,然后让FakeCollaboratorClass去公有继承CollaboratorClass,并且override那些虚函数,从而替换掉 CollaboratorClass中的行为。这种手法如下图所示。

  总体上讲,我们推荐优先使用“接口提取”手法,因为这将使代码遵循“依赖抽象原则”,从而使软件更好地应对今后的变化。但是,“Virtual and Override”方法也是有其一席之地的,我们后面会看到例子。

  2.2.3 “参数注入”和“Extract and Override”

  在ClassUnderTest这边,对CollaboratorClass的依赖的产生方式也可以划分成两类:

  依赖是通过方法参数传入的,这种形式的依赖被称为“参数注入”式依赖(parameter injection dependency)。参数注入式依赖是一种耦合度较低的依赖产生形式,因此对ClassUnderTest的影响不大,一般最多只需要把方法的签名由直接依赖CollaboratorClass改成依赖接口CollaboratorService。

  依赖是在被测方法的方法体内部产生的,这种依赖被称为“隐藏式”依赖(hidden dependency)。隐藏式依赖有多种表现形式:

  ● 直接创建CollaboratorClass对象作为局部变量或成员变量。

  ● 通过一个工厂方法来产生CollaboratorClass对象。

  ● 通过一个工厂类来产生CollaboratorClass对象。

  隐藏式依赖是一种耦合程度较高的依赖,因此是我们着重需要“打破”的依赖。一种方法是把隐藏式依赖转变成参数注入式依赖,我们将在后面的小节中看到这种方法的应用。而另一种方法,则是使用“Extract and Override”手法,即:我们给ClassUnderTest引入一个virtual的工厂成员函数,来返回一个CollaboratorClass对象的引用,然后对ClassUnderTest派生出一个子类,在该子类中override这个工厂成员函数,让它返回一个FakeCollaboratorClass对象的引用,如下图所示。

  这里的TestingClassUnderTest被称为“测试用子类”(Testing Subclass)。这时,在Test Fixture中被实例化的其实就是测试用子类,而不是被测类本身。

  以上我们研究了进行单元测试所需要的核心技术,以及4种最常用的核心手法,这些手法足以应付绝大多数的情况,但是,仍然有一些特殊情况需要我们特别注意,我们从下一节开始,以Q&A的形式,讨论这些特殊情况。

 2.3 应付构造函数中的隐藏式依赖

  当ClassUnderTest中出现隐藏式依赖时,最常用的有两种手法来打破这种依赖。我们分别来看一下。

  2.3.1 转变成参数注入式依赖

  对于像C#和Java这样的语言,由于它们支持在一个构造函数中去调用另一个构造函数,因此可以很方便地增加一个构造函数,把隐藏式依赖转变成参数注入式依赖。下面的UML图阐释了这种手法。

  而对于C++,由于它没有提供在构造函数中调用另一个构造函数的功能,因此通常的作法就是把公共的的初始化代码放入一个init()私有方法中去,让不同的构造函数去调用init()方法。

  2.3.2 使用“调包方法”

  还可以考虑给ClassUndertTest引入一个“调包方法”,使得测试类和测试方法可以很方便地将合作者类“调包”成伪合作者类。这里的“调包方法”本质上就是一个setter方法,但是为了表明这个特殊的setter方法只应该被测试类和测试方法调用,我们一般给调包方法命名为SupersedeCollaborator()。下图就是一个演示。

  这里必须要提醒的是,在C++中使用这个手法时必须注意在“调包方法”中不能引起资源泄漏,也就是说,必须先释放掉原有的合作者对象,再以伪合作者替代之。

  2.4 怎样测试ClassUnderTest中的private方法?

  如果要测试一个类的private方法,答案的总体思路是:适当打破访问权限。也就是说,我们可以把private方法声明成protected方法,然后对ClassUnderTest进行派生,得到一个“测试用子类”,在该“测试用子类”中声明一个public方法,该方法是ClassUnderTest中的protected方法的代理。这种手法可以用下图来表示。


 2.4 应付“全局依赖”

  所谓“全局依赖”,是指被测类或被测方法所依赖的合作者是一个“全局单件”,包括C#/Java/C++中的“单件类”(Singleton)和C++中的全局变量和自由方法(实际上, Singleton可视为全局变量在面向对象语言中的变种)。我们下面来看看怎么应对这些情况。

  2.4.1 使用“封装全局引用”手法来解除对全局变量和自由方法的依赖

  要打破对全局变量和自由方法的依赖,其实基本思想就是:把它们纳入到一个类中,让它们成为一个类的成员变量和成员方法。一旦把全局变量和自由函数封装进了某个类,那么我们就可以继续利用2.2.2节提到的两种手法来引入伪合作者了。

  2.4.2 使用“静态调包方法”来解除对单件类的依赖

  单件类往往具有3个特点:

  (1)单件类的构造函数通常被设为private。

  (2)单件类具有一个静态成员变量,该成员变量持有该类的唯一一个实例。

  (3)单件类具有一个静态方法,用来提供对单件实例的访问,通常该方法名叫getInstance()。

  由于单件类被设计成强制性地在整个程序中只有一份,因此要伪造它比较困难。我们推荐使用“静态调包方法”手法。这个手法的本质是适当打破单件类的单件性约束,把单件类的构造函数改为protected,使我们能够从单件类派生出一个Fake子类,然后为单件类引入一个静态调包函数SupersedeInstance(),以允许单件类中的静态成员变量被“调包”成Fake子类对象。下图表明了这一手法。

  同样必须强调的是,在C++中的使用这个手法的时候,必须保证没有资源泄漏。

  2.5 如果CollaboratorClass本身就处于继承体系中,怎么办?

  先设想一下CollaboratorClass本身存在基类的情况,如下图所示。


如果使用“Virtual and Override”手法来伪造合作者类,那么不存在任何问题,我们可以用下面的图来表示。

  另一方面,如果想使用“接口提取”手法的话,那么一种比较好的策略是使用“外覆类”(Wrapper Class),如下图所示。

  2.6 当CollaboratorClass是标准类库的一员时,怎么办?

  有些时候,我们的被测类所依赖的是特定操作系统(Windows, Unix, 或Mac)、特定标准规范(.NET,或J2EE)、特定函数库或类库(如POSIX API和Win32 API)及特定用户界面(CUI或者GUI)所提供的功能时,这实际上是引入了对特定平台的依赖性,而这往往都是在提示我们:应该加入一个更高层次的抽象(也即一个间接层,indirection),从而将这种对特定平台的依赖隐藏到这个抽象之后。换句话说,我们应该引入一个接口,来使我们的代码具有平台无关性,如下图所示。

  2.7 怎样测试抽象接口?

  假设我们的系统中定义了一个抽象接口ServiceInterface,系统中有两个类(分别是ServiceImpl1和ServiceImpl2)实现了这个接口。现在,我们希望为ServiceInteface抽象接口编写一个通用的测试类,这个测试类不仅能测试当前已经实现该接口的类,而且可以不加修改地应用于将来实现ServiceInteface接口的类。应该怎么办呢?下图展示了一种可能的方案。

  上图中,ServiceInterfaceTestFixture测试类中的测试方法都是基于ServiceInterface来进行测试的,不依赖于其具体实现类。这样就保证了仅测试抽象接口所定义的行为。当将来系统引入ServiceInteface的新的实现类时,只需要从ServiceInterfaceTestFixture类再派生出一个新子类,并实现createServiceInstance()方法来创建相应的对象即可。

posted @ 2011-10-09 22:08 顺其自然EVO| 编辑 收藏

 第一章 为什么使用单元测试

 第一章 为什么使用单元测试

  1.1 程序员的工作——修改软件

  修改既有代码是程序员谋生的手段。但是为什么我们需要去修改软件呢?修改软件有以下4个主要起因:

  ● 修正bug

  ● 添加新特性(feature)

  ● 改善设计

  ● 优化资源使用

  这4项都与软件的“行为”密切相关,见下表。


软件的既有行为
软件的新行为
修正bug
改变软件的既有行为
增加新行为
添加新特性
保持软件的既有行为,完全不修改既有代码
改善设计
保持软件的既有行为,但软件的可维护性得到提升
优化资源使用
保持软件的既有行为,但软件的性能得到提升

  通过这张表格我们看出:只有在修正bug时,我们才需要改变软件的既有行为,而在其他情况下,我们都需要保持住软件的既有行为。如果我们在改善设计,优化,或添加新特性时改变了软件的既有行为,那我们实际上是给软件引入了bug。

  可是程序员的工作就是修改软件,所以我们有很多的“机会”给软件引入bug。有什么办法能让我们的生活轻松一点,而不用因为修改代码引入bug而担惊受怕吗?

  1.2 软件夹钳——测试

  我们已经看到:在大多数情况下,我们希望对软件所做的改动不会改变系统的既有行为。即使是对于修正bug这种情况,我们也希望一旦bug被修正,那么修正后的正确行为能够得到保持,而不会被再之后的代码修改所改变。怎样做到这一点呢?

   让我们这样想想:如果我们能在对代码进行改动之前,用一种“软件夹钳”(software vise)来固定住软件的既有行为,那么我们就可以放心大胆地去修改代码了。那么,又是什么可以来充当“软件夹钳”呢?答案是:测试。我们可以这样想一 下:当一段代码被一组良好的测试所覆盖时,我们就可以放手去修改这段代码,并在修改完成之后立即运行这组测试,来验证我们的修改并没有改变既有行为而引入 bug。如果确实改变了既有行为,那么测试就会明确无误地发出警报。由此可见,测试就是程序员所需要的“软件夹钳”。

  1.3 单元测试与集成测试之争

  我们已经知道了“测试”就是程序员所需要的“软件夹钳”。但是测试分为单元测试和集成测试,程序员需要哪种测试呢?让我们来分析一下程序员需要什么样的测试,这样或许我们就能知道程序员应该更偏向于哪种测试了。

  ● 程序员需要的测试应该是能帮助程序员定位错误的,这样程序员才会真正地从测试中得到“实惠”。

  ● 程序员需要的测试应该是很容易执行的,最好是只需点击一个按钮或键入一条命令,这些测试就能运行,而无需费时费力地去搭建测试环境及准备测试数据或仪器。

  ● 程序员需要的测试应该是运行速度很快的,最好能在几分钟内完成,这样程序员才能快速地得到反馈,采取下一步动作。

  ● 程序员需要的测试应该是易于写就的,而不愿意对代码基大动干戈,这样程序员才会愿意去写这些测试。

  ● 程序员需要的测试应该是自动化的,可重复的。这样程序员才会愿意重复多次地去运行这种测试。

  比对程序员的需求,我们可以发现集成测试往往不能满足程序员的这些需求:

  ● 集成测试由于涉及多个模块,因此往往不能提供准确的错误定位信息。

  ● 集成测试的执行时间一般较长(小时级),这不能给程序员带来快速反馈。

  ● 集成测试可以自动化执行,但前提是把测试环境和测试数据等事先准备好。

  ● 集成测试由于不太容易写就,通常不是由程序员写就,而由专门的测试人员写就。

  相反,单元测试,尤其是良好的单元测试,恰恰正是程序员所需要的那种测试:

  单元测试针对单个类或单个方法,能很有成效地帮助程序员准确定位问题所在。

  单元测试应该是执行时间很短的,全部执行也只需5到10分钟,程序员正好可以去喝喝咖啡。

  单元测试是一种“虚拟”测试,重在测试代码逻辑,因此一般不需要真实测试环境和测试数据的支持。

  单元测试是很容易写就的,尤其是有单元测试框架的帮助时。

  由此可见,对于程序员而言,需要的是单元测试。程序员使用单元测试来充当软件夹钳,并在修改代码时获得快速反馈,从而更有信心地投入到修改软件的工作中。

  1.4 进行单元测试的其他好处

  我们已经知道了单元测试带来的一个好处:它可以充当程序员的“软件夹钳”,在程序员修改软件的过程中给予程序员快速的反馈,帮助程序员定位问题,避免引入bug。单元测试就只有这一个好处吗?不是的。下面我们就来看看引入单元测试带来的其他好处。

  1.4.1 单元测试是代码的“活文档”

  让文档及时反映软件设计和代码的最新情况,这是一个颇有挑战性的问题。一种较好的思路是:使用“内部”文档,即把文档同代码“拴”在一起。这样当代码发生改变的时候,文档也能相应更新。注释就是一种内部文档,良好的注释应该反映代码的最新状况。

  类比来看,单元测试同样也是内部文档,因为单元测试本质上也是描述了被测类或被测方法的行为。对软件行为不了解的程序员,可以通过阅读单元测试 代码来理解软件的行为。同注释相比,单元测试还具有一个更好的特性:它是一种“可执行”文档。如果单元测试在被执行时无法通过,那么说明要么单元测试没有 反映当前代码的真实状况,要么说明代码中有bug。无论哪种情况,程序员都需要修改某一方,以保持两者的一致。

  1.4.2 具有可测试性的软件具有更高的质量

  近年来流行的极限编程方法论推崇“测试驱动开发”。我们认为,“测试驱动开发”并不一定要求必须先有测试后有代码,而关键在于要求在设计软件和 实现编码时,一定要预先把软件的可测试性考虑周全。这种可测试性的重要体现就是能够方便地将单个类或方法纳入单元测试之中。具有可测试性的软件的质量往往 高于不具有可测试性的软件,为什么这样说呢?

  ● 一个类能够被方便地纳入单元测试,往往说明这个类职责单一,也就是说它满足“单一职责原则”。

  ● 一个类能够被方便地纳入单元测试,往往说明它与其他类之间的耦合程度较低,相互依赖性较小,而且很可能满足“依赖抽象原则”和“开放-封闭原则”。

  因此, 具有可测试性的软件,也更有可能是满足良好设计原则的软件,所以往往质量更高。

posted @ 2011-10-09 18:14 顺其自然EVO| 编辑 收藏

从单元测试到基于每日构造的自动测试

1、单元测试

  XXXX作为一个新项目,和其他所有项目一样,在开发工作进行之初就在考虑如何保证代码开发的质量。答案很容易找到:充分的单元测试。但是以前真正做得好得项目却不多。

  经过分析,总结了一下做好单元测试工作的四个要素:

  ――思想上的重视

  ――计划上保证

  ――测试手段保证

  ――测试效果的可验证

  1.1 思想上重视

  从以往的开发过程总结了一些教训:

  ――开发人员模块在交付联调前,测试不充分,导致联调周期较长

  ――代码进入维护期后,修改代码往往引起不可预期的错误。导致开发人员比较害怕在相对稳定的代码上进行修改。

  由于有这些教训,所以在编码之前,项目从领导到员工都很重视单元测试工作。

  1.2 计划上保证

  计划制定经过了XXXX的分解流程,XXXX分解小组成员讨论时充分意识到了单元测试工作的必要性,也意识到了为之要付出的代价。最终确定了单元测试和代码开发的比重为2:1左右。以操作维护的配置模块为例,最终制定的计划如下:

  从计划中可以看出,花在单元测试上的时间和代码开发的时间的比重甚至超过了2:1。

  附:表方法测试代码和被测代码的行数统计数据(该部分测试覆盖率已达95%以上)

  从数据看来,我们当初的估计是对的。测试代码行数和被测代码行数的比值大致是2:1

  1.3 测试手段保证

  1.3.1 测试框架

  对于一个系统工程来说,一个好的框架是至关重要的。一个好的框架应该有如下一些特点:

  ――结构清晰。不同的粒度层次在框架中一目了然

  ――可扩展性良好。

  我们选择单元测试工具就是为了满足能基于该工具搭建一个好的单元测试框架,另外还有以下几个方面的考虑:

  ――整个开发组负责三个模块:数据库,操作维护,传输。每个模块由不同的开发人员负责。从测试代码的可维护性考虑,需要统一各人编写测试代码的框架以及风格

  ――前台代码是由C语言编写的

  ――单元测试应该是可回归的,自动化的

  最终我们选择了Cunit,因为Cunit完全符合我们的要求。

Cunit是一种针对于C语言代码的单元测试框架,具有以下优点:

  –提供很多实用的接口函数(比如:各种断言、信息汇总、打印等)

  –简单、安全、方便

  –可添加任意多个测试单元

  –逐步添加,汇总成套,全面回归测试

  –完全免费

  测试框架选定以后,在组内进行了相应的培训,使得每个人的认识达到相同的水平,尽可能保证以后测试风格的一致。

  1.3.2 测试方案和测试规程

  单元测试是一个系统的工作,在进行之前需要一个总体的规划。这个工作在单元测试方案中得到体现;测试规程是对测试方案的细化,是单元测试的指导。测试规程可以在notes的测试管理平台中录入。

  1.3.3 自动化测试的考虑

  一个单元测试是由许多测试用例构成的,要实现可回归,自动化的测试,每个测试用例都应该可以按照一定的顺序连续的执行。要达到这个目标,就要求对整个单元测试做一个很好的规划。以数据库模块为例,我们做了如下的规划:

  ● 数据环境的建立

  数据库的测试依赖于一套数据表。单元测试一开始就需要在表中插入数据,以建立后续测试用例依赖的数据环境

  ● 测试粒度合理的划分

  我们把被测对象分为表方法、查询接口和修改接口。

  注:测试用例套(TestSuite)是Cunit的一个概念,用于封装测试用例套或者测试用例(TestCase)

  ● 在某种粒度上面保证数据环境的松耦合

  所有的测试用例基于数据环境编写,数据环境对于数据库的单元测试是及其重要的。每个测试用例从前一个用例继承数据环 境,又将本测试用例修改后的数据环境移交给下一个测试用例。从这个过程可以看出,数据环境在测试用例间是紧耦合的。如果测试用例一多,数据环境就会变得难 以控制,用例的编写将变得非常麻烦。尤其是多人分工合作的情况下,简直变成了不可能的任务。

  幸好我们有测试套!

  以表方法的测试套为例,在第二级粒度上,一个套就是一张表所有表方法测试用例的集合。这个套包含的测试用例大致在 4、5个左右。我们的策略是:二级粒度测试套内部的测试用例紧耦合数据环境,而二级粒度套之间的数据环境为松耦合。这通过在每个二级粒度套内加入一个数据 环境恢复用例来实现。

  这样一来,我们可以按照二级粒度的测试套来分工编写测试代码。

● 桩函数的考虑

  单元测试是不依赖其他模块的测试。但是实际代码中存在对其他模块函数的调用。为了消除这种依赖关系,就需要编写桩函数来替代对外部模块函数的调用。

  为了清晰起见,我们的桩函数集中在一个文件中。

  桩函数只是一个实现,至于原形定义,还是要引用其他模块的头文件,这样可以跟踪其他模块函数接口最新的改动。

  ● 单元测试工程的规划

  一个清晰合理的单元测试工程规划是必要的,这样有利于对象的查找和维护。

  以DBS模块的单元测试为例,我们的工程结构如下图所示。

  1.3.4 测试效果的可验证

  如何来量化单元测试的效果?

  覆盖率是一个最直观的指标。我们采用了Rational公司的Pure Coverage工具来获取覆盖率。这也是公司推荐的一个简单易上手的工具。我们要求测试覆盖率达到90%以上。

  2、自动测试

  2.1 每日构造

  每日构造是公司推行的一个最佳实践。它有一下一些功能:

  ● 开发人员及时把最新代码放入代码库,及时check out和check in,避免积累大量代码

  ● 及时进行模块间的整合,及时发现问题

  ● 对部分功能进行测试,无需等待

  ● 使用测试用例工具,对功能进行完整和重复的检验

  ● 记录所有程序问题

  ● 实现解决Bug的自动流程

  ● 提高正式版本提交的质量和速度

  ● XXXX在软件上开始推行每日构造。

  2.2 冒烟测试

  如果在每日构造的同时进行“冒烟测试”,能最早发现版本的问题。“冒烟测试”是微软提出的名词。其

  对象是每一个新编译的需要正式测试的软件版本,目的是确认软件基本功能正常,可以进行后续的正式测试工作。“冒烟测试“的执行者是版本编译人员。

  ZXWR-RNS网管项目已有用Robot在网管产品进行冒烟测试的实践。但是对于前台软件,公司内部尚未有这方面的尝试。

  2.3 自动测试

  基于前期模块的单元测试工作,一个自然的想法就是在每日构造的框架上运行模块的单元测试代码,实现前台软件的每日自动化测试。这是比“冒烟测试”更全面,更彻底的一个测试。

  2.3.1 单板软件

  模块1

  模块2

  模块n

  模拟测试工具

  XXXX软件的架构


  2.3.2 自动测试的规划

  2.3.2.1 自动测试的级别

  从软件的构成来考虑,分三级构造测试工程:

  ● 模块单元级:基本的软件单元

  ● 板级:由多模块组成的单板软件

  ● 系统集成级:由模拟测试软件和单板软件组成

  这三个级别的测试是互相补充的关系。每日构造进行三个级别的测试,这样就比较完整了。

  2.3.2.2 测试代码的管理

  要实现每日构造并进行自动测试,必须将测试代码也提升到被测代码同等的地位,纳入CC的管理。在CC上的目录结构如下所示:

  BoardSWS
  -BoardTest \\板级测试工程目录,包含测试用例代码以及板级测试工程
  -SystemTest \\系统集成级工程目录,不含测试用例,仅含工程
  -SCMM
  -UnitTest \\模块单元级测试目录,包含该级测试用例以及测试工程
  -SCMM源码
  -Cunit \\模块单元级测试用Cunit文件
  -其他模块
  -Cunit \\板级测试用Cunit文件

  SimuRNC
  -SystemTest \\系统集成级测试工程目录,包含测试工程和测试用例
  -SimuRNC源码
  -Cunit

  基于这样的目录结构,发布代码和测试代码在CC中纳入同一个VOB管理比较方便。这样一来,测试代码和发布代码执行相同的CC-CQ关联策略。也就是说,修改测试代码也需要和CQ关联。

  2.3.3 自动测试的执行

  自动测试由版本室完成。自动测试执行分为四个步骤:

  ● 同步CC的文件到本地

  ● 编译各测试工程(VC环境),输出编译结果文件

  ● 执行编译结果,输出测试执行结果文件

  ● 将编译结果导入

  notes

  上述步骤都是通过编写一个BAT文件挂在WINDOWS的“任务计划”中每日自动完成。

  附:
  BAT文件(DBS模块)
  编译结果 (DBS模块)
  测试执行结果(DBS模块)

  2.3.4 自动测试的目标

  依附于每日构造机制,自动依序运行三个级别的自动测试,测试产生的结果输出到文本文件。然后通过自动分析脚本提取该文件中没有通过的用例信息,发送邮件通知相关责任人进行分析处理。如此做到“日事日毕”,将故障扼杀在萌芽状态,保证代码质量的稳定。


posted @ 2011-10-09 17:29 顺其自然EVO| 编辑 收藏

单元测试的效益

单元测试的效益

  单元测试是针对代码单元,特别是算法密集的代码单元的独立测试,可以完整覆盖代码单元的功能逻辑,保证代码质量、降低成本、提高生产率、缩短开发周期、赢得市场先机、提升产品竞争力。

  单元测试分为静态和动态,静态方法只能发现小部分错误,例如,加法函数

  int add(int a, int b){return a-b;};

   加号写成了减号,这种最简单代码中的最简单错误,任何静态工具都无法发现,而动态方法只需输入两个1,自动判断输出是否等于2,马上就能发现错误。静态 方法能发现的错误,如除零错、数组越界、条件语句中==写成=,都会表现为异常或功能错误,动态方法当然也能发现,因此,动态方法是单元测试的根本方法。

  无处不在的80-20规则,在软件开发中 同样存在,例如,80%的错误存在于20%的代码中,80%的项目时间消耗在20%的代码上,当然这只是粗略的估计。“20%代码”就是逻辑复杂的代码, 也就是算法密集的代码。一个算法密集的函数,要对输入仔细分类,一个判定就是一次分类,嵌套的判定更使分类次数翻番,遗漏一个分类,或一个分类处理不正 确,就会造成错误。只有完整覆盖代码单元的所有输入等价类,才能保证发现这些错误,这在调试和系统测试中是难于做到的。算法密集的代码包含了项目中的大多数错误,即使只对这部分代码实施单元测试,也能产生理想的效益。

  除了保证代码质量,单元测试还具有排错成本最低、易于自动回归、缩短后续测试周期、提高编程效率等显著效益。

posted @ 2011-10-09 16:32 顺其自然EVO| 编辑 收藏

在Eclipse中使用JUnit

在Eclipse中使用JUnit

文章出处:CSDN 作者:rosen 发布时间:2005-10-24


这篇文章将向你介绍Junit,一个用来在项目中进行测试和调试的工具。在介绍完TDD(以测试驱动开发)理论后,将进一步讲解怎样在流行的Eclipse中建立你自己的JUnit测试。向你展示如何测试Hello World这样简单的程序。

   

    许 多书上都讨论了自动测试,但是只有很少的著作注意到这么一个问题,那就是怎样把这些测试组织起来。随着测试的增加,放置和调用这些测试却变得更加麻烦。这 将成为一个重要问题,以至于出现了TDD,极限编程(XP)使TDD得以普及。另外,你可以这样理解TDD:通过测试来开发。

   

    TDD的主要规范:

   

    在编写程序代码之前,与之对应的自动测试必须被写好。甚至程序代码并不存在,那也要看见一个失败的测试结果。

    在测试通过后,副本代码必须被丢弃。

   

    有一个具体步骤(可能指的是《Extreme Programming》)可以被任何一个程序员来参考,而不需要特殊的其他方法。在我们开始写测试之前,这些步骤(章节)应该被首先阅读——怎样组织自动测试。

   

    讲解一下不同种类的测试:

   

    单元测试:检测模块(也就是类)的正确性。如果对象需要访问外部的数据资源,例如数据库,就需要模拟一个mock objects,但在实际中真实数据与测试环境是不同的。

    客户测试:这是功能性、系统、和验收测试。用来测试整体的系统特性。在XP中,这些测试由用户编写。

    综合测试:介 于用户测试和单元测试之间的桥梁。综合测试帮助测试应用程序的交互性。一般情况下,mock objects不被用于综合测试,它会增加测试时间。同样,综合测试经常依赖特殊的测试环境,例如数据库送来的测试数据。综合测试也需要用到外部类库。例 如为J2EE应用程序进行综合测试的类库Cactus。解释这些测试超出了本文的范围,需要更加详细的信息请参考http://jakarta.apache.org/cactus/

    开发人员测试:这是用来让开发人员检验自己代码或新函数的。对于每一个开发人员,只要有可能,就需要有更多的测试来检验代码。组织这些测试和组织程序代码一样重要。

   

    在以下章节,只要提到“测试”,那就指的是开发人员测试。

    

    我们几乎准备好开始建立测试了,先应该为我们的测试选择名字。你也许会说,“这不是问题:把‘Test’这个字放在类名前面,就好了!”不会这么快!让我来说一下这个步骤存在的问题:

   

    在TDD中,被测试的类或者方法还不存在。

    一个测试能够覆盖多个方法,甚至多个类,这是可能的。

   

    以上只是一些普遍问题;还存在更多的问题。

   

    让我来提一个建议,在测试命名时:测试类的名字应该让人一眼就知道这是一个测试类,且能说明它要测试什么,注意是否和其他类重名。按照以上建议做,就很简单了,也不用担心名字太长或难听。

   

    即 将在Eclipse中用JUnit工具创建我们第一个测试了。假设你已经下载了一个最新的Eclipse版本。如果还没有,你应该去官方站点 http://www.eclipse.org下载。还需要JUnit,也可以从http://www.junit.org/下载。

   

    运行Eclipse。新建一个workplace项目,点击文件->新建->项目,选择Java项目,点击下一步。起一个项目名称,例如ProjectWithJUnit。点击完成。这样就完成新项目的建立了。再来配置一下Eclipse,在构建路径中添加JUnit类库。在工具条上点击项目->属性,选择Java构建路径,选择添加外部JAR,浏览Junit被存储的目录,选择junit.jar,点击打开。你将会看见JUnit出现在库的列表中。点击确定,让Eclipse重建路径。

   

    现在开发我们的“Hello World”例子。按照TDD的规则,应该在代码建立以前先把测试写好。为了能够在某出开始,我们假设未来的类名是HelloWorld,并且有一个方法Say(),这个方法返回String的值(例如“Hello World!”)。

   

    建立测试,在ProjectWithJUnit的标题上面点击右键,选择新建->其他,展开“Java”选项,选择JUnit。在右边的栏目对话框中选择测试案例,然后下一步。参考图1。

 

       

                    图1. 在Eclipse中建立JUnit测试

   

    在测试类这一栏中,写上将要被测试的类名HelloWorld。选择一个测试案例的名字,例如TestThatWeGetHelloWorldPrompt(是的,看上去很长,但是很清楚它的行为。)点击完成

   

    TestThatWeGetHelloWorldPrompt的代码如下:

 

    import junit.framework.TestCase;

 

    public class TestThatWeGetHelloWorldPrompt

    extends TestCase {

        public TestThatWeGetHelloWorldPrompt(

            String name) {

            super(name);

        }

        public void testSay() {

            HelloWorld hi = new HelloWorld();

            assertEquals("Hello World!", hi.say());

        }

        public static void main(String[] args) {

            junit.textui.TestRunner.run(

                TestThatWeGetHelloWorldPrompt.class);

        }

    }

 

    代码并不复杂;只是有点与众不同。然而,让我们考察一下细节。我们继承了JUnit的TestCase类,它在JUnit的javadocs定义为“运行众多测试的夹具。”JUnit也有TestSuite类,它是一组测试案例的集合,但在本文中不做讨论。

   

    建立测试案例的步骤如下:

   

    1、建立一个junit.framework.TestCase的实例。

    2、定义一些以“test”开头的无返回方法(例如testWasTransactionSuccessful(),testShow(),等等)。

   

    TestThatWeGetHelloWorldPrompt.java包含这些:TestCase的子类和一个叫做testSay()的方法。这个方法调用了assertEquals()函数,它用来比较我们预期的值和由say()返回的值。

   

    main() 方法用来运行测试和显示输出的。JUnit的TestRunner处理测试,提供基于图像和文本的输出表现形式。我们使用基于文本的版本,因为 Eclipse支持它,且也适合我们。当开始运行后,基于文本的版本测试会以文本形式输出,Eclipse会把这些输出自动变成图像界面的输出。

   

    按照TDD规范,首次运行测试,应该故意让它失败。点击运行->运行为->Junit测试(记住TestThatWeGetHelloWorldPrompt.java应该被突出的显示在包资源管理器中)。在左边窗口,应该看见JUnit窗口而不是包资源管理器,它显示一个红条,一次失败的测试,具体的失败原因参看图2。如果没有自动显示这些内容,点击JUnit标签(在底部的左边)。

 

          

                    图2. JUnit中失败的测试

   

    很好!的却失败了。现在我们来建立被测试代码:在包资源管理器窗口的ProjectWithJUnit标题上右击,选择新建->。选择类名,我们已经假设了它叫HelloWorld,然后直接点击完成。为HelloWorld.java填入下列代码:

 

        public class HelloWorld {

            public String say() {

                return("Hello World!");

            }

        }

       

    这段代码很简单,甚至不需要注解,我们再来看看结果。按照上面描述过的方式,在JUnit的窗口中显示了一个绿条,参看图3。绿条证明测试成功。

 

          

                     图3. JUnit中成功的测试

                              

    现在,我们想再让测试失败一次,但原因不同。这有助于展示JUnit测试中不同的报错信息。修改assertEquals()代码,把“Hello World!”变成“Hello Me!”。当再次运行JUnit时,结果变成了红条,在JUnit窗口的底部输出了失败原因,参看图4。

 

           

                    图4. JUnit中的ComparisonError

                             

    最 后,我想说一下关于测试是开发过程中的必要部分的话题。测试代码一直是开发中的重要部分。经过近几年的发展,已得到了很大的提高,这要归功于强大的理论研 究(比如“expectations-based development”等等),和快速发展的测试工具包,还有测试过程的改进。如果你对这篇文章感兴趣,那请你花一些时间来正式的学习一下测试理论吧, 这对你的工作很有用。

posted @ 2011-10-09 15:44 顺其自然EVO| 编辑 收藏

仅列出标题
共394页: First 上一页 386 387 388 389 390 391 392 393 394 下一页 
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

常用链接

留言簿(55)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜