软件测试就是在软件交付用户使用或投入运行前,对软件需求规格说明、设计规格说明和编码的最终复审,是软件质量保证的关键步骤。软件测试是为了发现错误而执行程序的过程。软件测试在软件生命周期中横跨两个阶段:通常在编写出每一个模块之后就需要对它做必要的测试(称为单元测试)。编码和单元测试属于软件生命周期中的同一个阶段。在结束这个阶段后对软件系统还要进行各种综合测试,如集成测试、系统测试、性能测试和配置测试等,这是软件生命周期的另一个独立阶段,即测试阶段。
软件测试的目的:
1、测试的最终目的是为了避免错误的发生,确保应用程序能够正常高效的运行;
2、好的测试用例在于发现至今未发现的错误;
3、成功的测试是发现了至今未发现的错误的测试;
4、好的测试工程师应该做到不仅发现问题,还能够帮助开发人员分析问题;
软件测试的原则:
1、应把“尽早和不断地进行软件测试”作为软件开发者的座右铭,实践证明单元测试能够尽早发现问题,减少后期测试的错误量。可以采用Junit和Jtest来辅助进行单元测试。
2、测试用例应由测试输入数据、测试执行步骤和与之对应的预期输出结果三部分组成。
3、应当避免由程序员检查自己的程序。(指后期系统测试阶段,不包括单元测试)
4、测试用例的设计要确保能覆盖所有可能路径。在设计测试用例时,应当包括合理的输入条件和不合理的输入条件。不合理的输入条件是指异常的,临界的,可能引起问题的输入条件。
5、充分注意测试中的群集现象。经验表明,测试后程序残存的错误数目与该程序中已发现的错误数目或检错率成正比。应该对错误群集的程序段进行重点测试。
6、严格执行测试计划,排除测试的随意性。
测试计划应包括:所测软件的功能,输入和输出,测试内容,各项测试的进度安排,资源要求,测试资料,测试工具,测试用例的选择,测试的控制方法和过程,系统的配置方式,跟踪规则,调试规则,以及回归测试的规定等等以及评价标准。
7、应当对每一个测试结果做全面的检查。
8、妥善保存测试计划,测试用例,出错统计和最终分析报告,为维护提供方便。
软件测试的对象:
软件测试并不单纯等同于程序测试。软件测试应该贯穿整个软件定义与开发整个期间。因此需求分析、概要设计、详细设计以及程序编码等各阶段所得到的文档,包括需求规格说明、概要设计规格说明、详细设计规格说明以及源程序,都应该是软件测试(评审)的对象。
在对需求理解与表达的正确性、设计与表达的正确性、实现的正确性以及运行的正确性的验证中,任何一个环节发生了问题都可能在软件测试中表现出来。
什么是单元测试
单元测试是在软件开发过程中要进行的最低级别的测试活动,测试的对象是软件设计的最小单位——模块。单元测试的依据是详细设描述,单元测试应对模块内所有重要的控制路径设计测试用例,以便发现模块内部的错误。在单元测试活动中,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。单元测试多采用白盒测试技术,系统内多个模块可以并行地进行测试。
在一种传统的结构化编程语言中,比如C,要进行测试的单元一般是函数或子过程。在象C++这样的面向对象的语言中,要进行测试的基本单元是类。对Ada语言来说,开发人员可以选择是在独立的过程和函数,还是在Ada包的级别上进行单元测试。单元测试的原则同样被扩展到第四代语言(4GL)的开发中,在这里基本单元被典型地划分为一个菜单或显示界面。
单元测试不仅仅是作为无错编码一种辅助手段在一次性的开发过程中使用,单元测试必须是可重复的,无论是在软件修改,或是移植到新的运行环境的过程中。因此,所有的测试都必须在整个软件系统的生命周期中进行维护。
经常与单元测试联系起来的另外一些开发活动包括代码走读(Code review),静态分析(Static analysis)和动态分析(Dynamic analysis)。静态分析就是对软件的源代码进行研读,查找错误或收集一些度量数据,并不需要对代码进行编译和执行。动态分析就是通过观察软件运行时的动作,来提供执行跟踪,时间分析,以及测试覆盖度方面的信息。
2、单元测试任务
单元测试任务包括:1 模块接口测试;2 模块局部数据结构测试;3 模块边界条件测试;4 模块中所有独立执行通路测试;5 模块的各条错误处理通路测试。
模块接口测试是单元测试的基础。只有在数据能正确流入、流出模块的前提下,其他测试才有意义。测试接口正确与否应该考虑下列因素:
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出现上溢、下溢和地址异常。
除了局部数据结构外,如果可能,单元测试时还应该查清全局数据(例如FORTRAN的公用区)对模块的影响。
在模块中应对每一条独立执行路径进行测试,单元测试的基本任务是保证模块中每条语句至少执行一次。此时设计测试用例是为了发现因错误计算、不正确的比较和不适当的控制流造成的错误。此时基本路径测试和循环测试是最常用且最有效的测试技术。计算中常见的错误包括:
1 误解或用错了算符优先级;
2混合类型运算;
3变量初值错;
4精度不够;
5表达式符号错。
比较判断与控制流常常紧密相关,测试用例还应致力于发现下列错误:
1不同数据类型的对象之间进行比较;
2错误地使用逻辑运算符或优先级;
3因计算机表示的局限性,期望理论上相等而实际上不相等的两个量相等;
4比较运算或变量出错;
5循环终止条件或不可能出现;
6迭代发散时不能退出;
7错误地修改了循环变量。
一个好的设计应能预见各种出错条件,并预设各种出错处理通路,出错处理通路同样需要认真测试,测试应着重检查下列问题:
1输出的出错信息难以理解;
2记录的错误与实际遇到的错误不相符;
3在程序自定义的出错处理段运行之前,系统已介入;
4异常处理不当;
5错误陈述中未能提供足够的定位出错信息。
边界条件测试是单元测试中最后,也是最重要的一项任务。众的周知,软件经常在边界上失效,采用边界值分析技术,针对边界值及其左、右设计测试用例,很有可能发现新的错误。
3、单元测试的过程与环境
一般认为单元测试应紧接在编码之后,当源程序编制完成并通过复审和编译检查,便可开始单元测试。测试用例的设计应与复审工作相结合,根据设计信息选取测试数据,将增大发现上述各类错误的可能性。在确定测试用例的同时,应给出期望结果。
应为测试模块开发一个驱动模块(driver)和(或)若干个桩模块(stub),下图显示了一般单元测试的环境。驱动模块在大多数场合称为"主程序",它接收测试数据并将这些数据传递到被测试模块,被测试模块被调用后,"主程序"打印"进入-退出"消息。
驱动模块和桩模块是测试使用的软件,而不是软件产品的组成部分,但它需要一定的开发费用。若驱动和桩模块比较简单,实际开销相对低些。遗憾的是,仅用简单的驱动模块和桩模块不能完成某些模块的测试任务,这些模块的单元测试只能采用下面讨论的综合测试方法。
提高模块的内聚度可简化单元测试,如果每个模块只能完成一个,所需测试用例数目将显著减少,模块中的错误也更容易发现。
三、集成测试的基本方法
时常有这样的情况发生,每个模块都能单独工作,但这些模块集成在一起之后却不能正常工作。主要原因是,模块相互调用时接口会引入许多新问题。例如,数据经过接口可能丢失;一个模块对另一模块可能造成不应有的影响;几个子功能组合起来不能实现主功能;误差不断积累达到不可接受的程度;全局数据结构出现错误,等等。综合测试是组装软件的系统测试技术,按设计要求把通过单元测试的各个模块组装在一起之后,进行综合测试以便发现与接口有关的各种错误。
某设计人员习惯于把所有模块按设计要求一次全部组装起来,然后进行整体测试,这称为非增量式集成。这种方法容易出现混乱。因为测试时可能发现一大堆错误,为每个错误定位和纠正非常困难,并且在改正一个错误的同时又可能引入新的错误,新旧错误混杂,更难断定出错的原因和位置。与之相反的是增量式集成方法,程序一段一段地扩展,测试的范围一步一步地增大,错误易于定位和纠正,界面的测试亦可做到完全彻底。
下面讨论两种增量式集成方法。
1、自顶向下集成
自顶向下集成是构造程序结构的一种增量式方式,它从主控模块开始,按照软件的控制层次结构,以深度优先或广度优先的策略,逐步把各个模块集成在一起。深度优先策略首先是把主控制路径上的模块集成在一起,至于选择哪一条路径作为主控制路径,这多少带有随意性,一般根据问题的特性确定。以下图为例,若选择了最左一条路径,首先将模块M1,M2,M5和M8集成在一起,再将M6集成起来,然后考虑中间和右边的路径。广度优先策略则不然,它沿控制层次结构水平地向下移动。仍以下图为例,它首先把M2、M3和M4与主控模块集成在一起,再将M5和M6 和其他模块集资集成起来。
自顶向下综合测试的具体步骤为:
1 以主控模块作为测试驱动模块,把对主控模块进行单元测试时引入的所有桩模块用实际模块替代; 2 依据所选的集成策略(深度优先或广度优先),每次只替代一个桩模块; 3 每集成一个模块立即测试一遍; 4 只有每组测试完成后,才着手替换下一个桩模块; 5 为避免引入新错误,须不断地进行回归测试(即全部或部分地重复已做过的测试)。 从第二步开始,循环执行上述步骤,直至整个程序结构构造完毕。下图中,实线表示已部分完成的结构,若采用深度优先策略,下一步将用模块M7替换桩模块S7,当然M7本身可能又带有桩模块,随后将被对应的实际模块一一替代。
自顶向下集成的优点在于能尽早地对程序的主要控制和决策机制进行检验,因此较早地发现错误。缺点是在测试较高层模块时,低层处理采用桩模块替代,不能反映真实情况,重要数据不能及时回送到上层模块,因此测试并不充分。解决这个问题有几种办法,第一种是把某些测试推迟到用真实模块替代桩模块之后进行,第二种是开发能模拟真实模块的桩模块;第三种是自底向上集成模块。第一种方法又回退为非增量式的集成方法,使错误难于定位和纠正,并且失去了在组装模块时进行一些特定测试的可能性;第二种方法无疑要大大增加开销;第三种方法比较切实可行,下面专门讨论。
2、自底向上集成
自底向上测试是从"原子"模块(即软件结构最低层的模块)开始组装测试,因测试到较高层模块时,所需的下层模块功能均已具备,所以不再需要桩模块。
自底向上综合测试的步骤分为:
1 把低层模块组织成实现某个子功能的模块群(cluster); 2 开发一个测试驱动模块,控制测试数据的输入和测试结果的输出; 3 对每个模块群进行测试; 4 删除测试使用的驱动模块,用较高层模块把模块群组织成为完成更大功能的新模块群。 从第一步开始循环执行上述各步骤,直至整个程序构造完毕。 下图说明了上述过程。首先"原子"模块被分为三个模块群,每个模块群引入一个驱动模块进行测试。因模块群1、模块群2中的模块均隶属于模块Ma,因此在驱动模块D1、D2去掉后,模块群1与模块群2直接与Ma接口,这时可对MaD3被去掉后,M3与模块群3直接接口,可对Mb进行集成测试,最后Ma、Mb和 Mc全部集成在一起进行测试。
自底向上集成方法不用桩模块,测试用例的设计亦相对简单,但缺点是程序最后一个模块加入时才具有整体形象。它与自顶向综合测试方法优缺点正好相反。因此,在测试软件系统时,应根据软件的特点和工程的进度,选用适当的测试策略,有时混和使用两种策略更为有效,上层模块用自顶向下的方法,下层模块用自底向上的方法。
此外,在综合测试中尤其要注意关键模块,所谓关键模块一般都具有下述一或多个特征:①对应几条需求;②具有高层控制功能;③复杂、易出错;④有特殊的性能要求。关键模块应尽早测试,并反复进行回归测试。
四、确认测试的基本方法
通过集成测试之后,软件已完全组装起来,接口方面的错误也已排除,确认测试即可开始。确认测试应检查软件能否按合同要求进行工作,即是否满足软件需求说明书中的确认标准。
1. 确认测试标准 实现软件确认要通过一系列墨盒测试。确认测试同样需要制订测试计划和过程,测试计划应规定测试的种类和测试进度,测试过程则定义一些特殊的测试用例,旨在说明软件与需求是否一致。无是计划还是过程,都应该着重考虑软件是否满足合同规定的所有功能和性能,文档资料是否完整、准确人机界面和其他方面(例如,可移植性、兼容性、错误恢复能力和可维护性等)是否令用户满意。 确认测试的结果有两种可能,一种是功能和性能指标满足软件需求说明的要求,用户可以接受;另一种是软件不满足软件需求说明的要求,用户无法接受。项目进行到这个阶段才发现严重错误和偏差一般很难在预定的工期内改正,因此必须与用户协商,寻求一个妥善解决问题的方法。
2. 配置复审 确认测试的另一个重要环节是配置复审。复审的目的在于保证软件配置齐全、分类有序,并且包括软件维护所必须的细节。
3. α、β测试 事实上,软件开发人员不可能完全预见用户实际使用程序的情况。例如,用户可能错误的理解命令,或提供一些奇怪的数据组合,亦可能对设计者自认明了的输出信息迷惑不解,等等。因此,软件是否真正满足最终用户的要求,应由用户进行一系列"验收测试"。验收测试既可以是非正式的测试,也可以有计划、有系统的测试。有时,验收测试长达数周甚至数月,不断暴露错误,导致开发延期。一个软件产品,可能拥有众多用户,不可能由每个用户验收,此时多采用称为α、β测试的过程,以期发现那些似乎只有最终用户才能发现的问题。 α测试是指软件开发公司组织内部人员模拟各类用户行对即将面市软件产品(称为α版本)进行测试,试图发现错误并修正。α测试的关键在于尽可能逼真地模拟实际运行环境和用户对软件产品的操作并尽最大努力涵盖所有可能的用户操作方式。经过α测试调整的软件产品称为β版本。紧随其后的β测试是指软件开发公司组织各方面的典型用户在日常工作中实际使用β版本,并要求用户报告异常情况、提出批评意见。然后软件开发公司再对β版本进行改错和完善。
五、系统测试的基本方法
计算机软件是基于计算机系统的一个重要组成部分,在系统测试之前,软件工程师应完成下列工作:
为测试软件系统的输入信息设计出错处理通路;
设计测试用例,模拟错误数据和软件界面可能发生的错误,记录测试结果,为系统测试提供经验和帮助;
参与系统测试的规划和设计,保证软件测试的合理性。
系统测试应该由若干个不同测试组成,目的是充分运行系统,验证系统各部件是否都能政党工作并完成所赋予的任务。
下面简单讨论几类系统测试。
1、恢复测试 恢复测试主要检查系统的容错能力。当系统出错时,能否在指定时间间隔内修正错误并重新启动系统。恢复测试首先要采用各种办法强迫系统失败,然后验证系统是否能尽快恢复。对于自动恢复需验证重新初始化(reinitialization)、检查点(checkpointing mechanisms)、数据恢复(data recovery)和重新启动 (restart)等机制的正确性;对于人工干预的恢复系统,还需估测平均修复时间,确定其是否在可接受的范围内。
2、安全测试 安全测试检查系统对非法侵入的防范能力。安全测试期间,测试人员假扮非法入侵者,采用各种办法试图突破防线。例如,①想方设法截取或破译口令;②专门定做软件破坏系统的保护机制;③故意导致系统失败,企图趁恢复之机非法进入;④试图通过浏览非保密数据,推导所需信息,等等。理论上讲,只要有足够的时间和资源,没有不可进入的系统。因此系统安全设计的准则是,使非法侵入的代价超过被保护信息的价值。此时非法侵入者已无利可图。
3、强度测试 强度测试检查程序对异常情况的抵抗能力。强度测试总是迫使系统在异常的资源配置下运行。例如,①当中断的正常频率为每秒一至两个时,运行每秒产生十个中断的测试用例;②定量地增长数据输入率,检查输入子功能的反映能力;③运行需要最大存储空间(或其他资源)的测试用例;④运行可能导致虚存操作系统崩溃或磁盘数据剧烈抖动的测试用例,等等。
4、 性能测试 对于那些实时和嵌入式系统,软件部分即使满足功能要求,也未必能够满足性能要求,虽然从单元测试起,每一测试步骤都包含性能测试,但只有当系统真正集成之后,在真实环境中才能全面、可靠地测试运行性能系统性能测试是为了完成这一任务。性能测试有时与强度测试相结合,经常需要其他软硬件的配套支持。
六、软件测试自动化的一些具体做法
因为软件测试的工作量很大(40% 到60% 的总开发时间),而又有很大部分适于自动化,因此,测试的改进会对整个开发工作的质量、成本和周期带来非常显著的效果。
下面举出一些测试自动化的例子:
1. 测试个案(test case ,或称为测试用例)的生成
用编程语言或更方便的剧本语言(script language 例如Perl等)写出短小的程序来产生大量的测试输入(包括输入数据与操作指令)。或同时也按一定的逻辑规律产生标准输出。输入与输出的文件名字按规定进行配对,以便控制自动化测试及结果核对的程序易于操作。 这里提到测试个案的命名问题,如果在项目的文档设计中作统一规划的话,软件产品的需求与功能的命名就应该成为后继开发过程的中间产品的命名分类依据。这样,就会为文档管理和配置管理带来很大的方便,使整个产品的开发过程变得更有条理,更符合逻辑。任何新手半途加入到开发工作中也会更容易进入状态。
2. 测试的执行写控制
单元测试或集成测试可能多用单机运行。但对于系统测试或回归测试,就极有可能需要多台机在网络上同时运行。记住一个这样的原则,在开发过程中的任何时候,如果你需要等候测试的运行结果的话,那就是一个缩短开发时间的机会。对于单个的测试运行,挖潜的机会在测试的设置及开始运行和结果的对比及显示。有时候,需要反复修改程序,重新汇编和重新测试。这样,每一个循环的各种手工键入的设置与指令所花费的时间,加起来就非常可观。如果能利用make或类似的软件工具来帮助,就能节省大量的时间。 对于系统测试或回归测试这类涉及大量测试个案运行的情况,挖潜的的机会除了利用软件工具来实现自动化之外,就是怎样充分利用一切硬件资源。往往,就算是在白天的工作时间内,每台计算机的负荷都没有被充分利用。能够把大量测试个案分配到各台机器上去同时运行,就能节省大量的时间。另外,把大量的系统测试及回归测试安排到夜间及周末运行,更能提高效率。如果不购买商品化的工具的话,应当遵从正规的软件开发要求来开发出好的软件测试自动化工具。在实践中,许多企业自行开发的自动化工具都是利用一些现成的软件工具再加上自己写的程序而组成的。这些自己开发的工具完全是为本企业量身定做的,因此可用性非常强。同时,也能根据需要随时进行改进,而不必受制于人。在设计软件自动测试工具的时候,路径(path)控制是一个非常重要的功能。理想的使用情况是:这个工具可以在任何一个路径位置上运行,可以到任何路径位置去取得测试用例,同时也可以把测试的结果输出放到任何的路径位置上去。这样的设计,可以使不同的测试运行能够使用同一组测试用例而不至于互相干扰,也可以灵活使用硬盘的空间,并且使备份保存工作易于控制。同时,软件自动测试工具必须能够有办法方便地选择测试用例库中的全部或部分来运行,也必须能够自由地选择被测试的产品或中间产品采作为测试对象。
3. 测试结果与标准输出的对比
在设计测试用例的时候,必须考虑到怎样才能够易于对此测试结果和标准输出。输出数据量的多少及数据格式对比较的速度有直接影响。而另一方面,也必须考虑到输出数据与测试用例的测试目标的逻辑对应性及易读性,这将会大大有利于分析测试所发现的不吻合,也有利于测试用例的维护。 许多时候,要写一些特殊的软件来执行测试结果与标准输出的对比工作,因为可能有部分的输出内容是不能直接对比的(比如,对运行的日期时间的记录,对运行的路径的记录,以及测试对象的版本数据等),就要用程序进行处理。
4. 不吻合的测试结果的分析、分类、记录和通报
上一点所谈到的,用于对测试结果与标准输出进行对比的特殊软件,往往也同时担任对不吻合的测试结果进行分析、分类、记录和通报的任务。"分析"是找出不吻合的地方并指出错误的可能起因。"分类"包括各种统计上的分项,例如,对应的源程序的位置,错误的严重级别(提示、警告、非失效性错误、失效性错误;或别的分类方法),新发现的还是已有记录的错误,等等。"记录",是按分类存档。"通报",是主动地对测试的运行者及测试用例的"负责人"通报出错的信息。 这里提到测试用例的"负责人"的概念。是用以指定一个测试用例运行时发现的缺陷,由哪一个开发人员负责分析(有时是另外的开发人员引进的缺陷而导致的错误)及修复。在设立测试用例库时,各用例均应有指定的负责人。 最直接的通报方法是由自动测试软件发出电子邮件给测试运行者及测试用例负责人。邮件内容的详细程度可根据需要灵活决定。
5. 总测试状况的统计,报表的产生
这些都是自动测试工具所应有的功能。目的是提高过程管理的质量,同时节省用于产生统计数据的时间。产生出来的统计报表,最好是存放到一个约定的路径位置,以便任何有关人员都知道怎样查阅。同时,可按需要用电子邮件向适当的对象(如项目经理,测试经理和质量保证经理)寄出统计报表。
不可以,软件的开发也是从小的模块开始,不可能没有模块就开始集成,后来才打包成一个软件,形成一个系统。
单元测试是测试各个小的模块,通过对他们的测试,才能找出基本的bug,然后为各个模块搭建接口,也就是把模块组装起来,之后进行集成测试,看各个模块的接口是否正常稳定,打包成软件后,先做出一个demo版本,由开发和测试一起进行系统测试。