一.  软件測试方法

1.        软件測试方法包含:白盒測试(White  Box  Testing)、黑盒測试(Black  Box Testing)、灰盒測试、静态測试、动态測试。

2.        白盒測试:是一种測试用例设计方法。在这里盒子指的是被測试的软件,白盒。顾名思义即盒子是可视的,你能够清晰盒子内部的东西以及里面是怎样运作的。因此白盒測试须要你对系统内部的结构和工作原理有一个清晰的了解,并且基于这个知识来设计你的用例。

白盒測试技术一般可被分为静态分析和动态分析两类技术。

静态分析主要有:控制流分析技术、数据流分析技术、信息流分析技术。

动态分析主要有:逻辑覆盖率測试(分支測试、路径測试等),程序插装等。

白盒測试优点:迫使測试人员去细致的思考软件的实现;能够检測代码中的每条分支和路径。揭示隐藏在代码中的错误;对代码的測试比較彻底。最优化。

白盒測试缺点:昂贵。无法检測代码中遗漏的路径和数据敏感性错误;不验证规格的正确性。

3.        黑盒測试又叫功能測试。这是因为在黑盒測试中主要关注被測软件的功能实现,而不是内部逻辑。在黑盒測试中。被測对象的内部结构,运作情况对測试人员是不可见的,測试人员对被測产品的验证主要是依据其规格。验证其与规格的一致性。

在绝大多数没实用户參与的黑盒測试中。最常见的測试有:功能性測试、容量測试、安全性測试、负载測试、恢复性測试、标杆測试、稳定性測试、可靠性測试等。

4.        灰盒測试:白盒測试和黑盒測试往往不是决然分开的,一般在白盒測试中交叉使用黑盒測试的方法,在黑盒測试中交叉使用白盒測试的方法。灰盒測试就是这类界于白盒測试和黑盒測试之间的測试。

最常见的灰盒測试是集成測试

5.        静态測试:是一种不通过运行程序而进行測试的技术。它的关键功能是检查软件的表示和描写叙述是否一致,没有冲突或者没有歧义。

6.        动态測试:包含了程序在受控的环境下使用特定的期望结果进行正式的运行。它显示了一个系统在检查状态下是正确还是不对。

单元測试属于白盒測试范畴。集成測试属于灰盒測试范畴。系统測试属于黑盒測试范畴

二.  单元測试

1.        概念:单元測试(Unit Testing)是对软件基本组成单元进行的測试,如函数或是一个类的方法。这里的单元。就是软件设计的最小单位。

单元測试的两个步骤:人工静态检查法与动态运行跟踪法。

人工静态检查是測试的第一步,这个阶段工作主要是保证代码算法的逻辑正确性(尽量通过人工检查发现代码的逻辑错误)、清晰性、规范性、一致性、算法高效性,并尽可能的发现程序中没有发现的错误。

第二步是通过设计測试用例,运行待測程序来跟踪比較实际结果与预期结果来发现错误。

2.      人工检查:

(1)、检查算法的逻辑正确性:确定所编写的代码算法、数据结构定义(如:队列、堆栈等)是否实现了模块或方法所要求的功能。

(2)、模块接口的正确性检查:确定形式參数个数、数据类型、顺序是否正确;确定返回值类型及返回值的正确性。

(3)、输入參数有没有作正确性检查:假设没有作正确性检查,确定该參数是否的确无需做參数正确性检查,否则请加入上參数的正确性检查。

(4)、调用其它方法接口的正确性:检查实參类型正确与否、传入的參数值正确与否、个数正确与否。特别是具有多态的方法。返回值正确与否。有没有误解返回值所表示的意思。最好对每一个被调用的方法的返回值用显示代码作正确性检查。假设被调用方法出现异常或错误程序应该给予反馈,并加入适当的出错处理代码。

(5)、出错处理:模块代码要求能预见出错的条件,并设置适当的出错处理,以便一旦程序出错时,能对出错程序重做安排,保证其逻辑的正确性,这样的出错处理应当是模块功能的一部分。

若出现下列情况之中的一个。则表明模块的错误处理功能包含有错误或缺陷:出错的描写叙述难以理解;出错的描写叙述不足以对错误定位。不足以确定出错的原因;显示的错误信息与实际的错误原因不符。对错误条件的处理不对;在对错误进行处理之前。错误条件已经引起系统的干预等。

(6)、保证表达式、SQL语句的正确性:检查所编写的SQL语句的语法、逻辑的正确性。对表达式应该保证不含二义性。对于easy产生歧义的表达式或运算符优先级(如:<、=、 >、 &&、||、++、 --等)能够採用扩号“()”运算符避免二义性。这样一方面能够保证代码的正确可靠,同一时候也能够提高代码的可读性。

(7)、检查常量或全局变量使用的正确性:确定所使用的常量或全局变量的取值和数值、数据类型。保证常量每次引用同它的取值、数值和类型的一致性。

(8)、表示符定义的规范一致性:保证变量命名能够见名知意。并且简洁但不宜过长或过短、规范、easy记忆、最好能够拼读。并尽量保证用同样的表示符代表同样功能。不要将不同的功能用同样的表示符表示。更不要用同样的表示符代表不同的功能意义。

(9)、程序风格的一致性、规范性:代码必须能保证符合企业规范,保证全部成员的代码风格一致、规范、工整。比如对数组做循环。不要一会儿採用下标变量从下到上的方式(如:for(i=0;i++;i<10))。一会儿又採用从上到下的方式(如:for(i=10;i--;i>0))。应该尽量採用统一的方式,或则统一从下到上,或则统一从上到下。建议採用for循环和While循环。不要採用do{}while循环等。

(10)、检查程序中使用到的神奇数字是否採用了表示符定义:神奇的数字包含各种常数、数组的大小、字符位置、变换因子以及程序中出现的其它以文字形式写出的数值。在程序源码里,一个具有原本形式的数对其本身的重要性或作用没提供不论什么指示性信息,它们也导致程序难以理解和改动。对于这类神奇数字必须採用对应的标量来表示;假设该数字在整个系统中都可能使用到务必将它定义为全局常量;假设该神奇数字在一个类中使用可将其定义为类的属性(Attribute),假设该神奇数字仅仅在一个方法中出现务必将其定义为局部变量或常量。

(11)、检查代码能否够优化、算法效率是否最高:如:SQL语句能否够优化,能否够用1条SQL语句取代程序中的多条SQL语句的功能,循环是否必要,循环中的语句能否够抽出到循环之外等。

(12)、检查您的程序是否清晰简洁easy理解:注意:冗长的程序并不一定不是清晰的。

(13)、检查方法内部凝视是否完整:是否清晰简洁;是否正确的反映了代码的功能,错误的凝视比没有凝视更糟;是否做了多余的凝视。对于简单的一看就懂的代码没有必要凝视。

(14)、检查凝视文档是否完整:对包、类、属性、方法功能、參数、返回值的凝视是否正确且easy理解。是否会落了或多了某个參数的凝视,參数类型是否正确,參数的限定值是否正确。特别是对于形式參数与返回值中关于神奇数值的凝视,如:类型參数 应该指出 1.代表什么,2.代表什么,3.代表什么等。对于返回结果集(Result Set)的凝视。应该凝视结果集中包含那些字段及字段类型、字段顺序等。

3.        动态运行跟踪:动态运行測试通常分为黑盒測试与白盒測试。对于单元測试来说主要应该採用白盒測试法对每一个模块的内部作跟踪检查測试。

对于单元白盒測试。应该对程序模块进行例如以下检查:(1)、对模块内全部独立的运行路径至少測试一次;(2)、对全部的逻辑判定。取“真”与“假”的两种情况都至少运行一次;(3)、在循环的边界和运行界限内运行循环体;(4)、測试内部数据的有效性等等。

4.      单元測试的目的:在于发现各模块内部可能存在的各种错误。主要是基于白盒測试。

单元測试的目的主要有3方面:验证单元代码和具体设计文档的一致性;跟踪具体设计文档中设计的实现。发现具体设计文档中存在的错误。发如今编码过程中引入的错误。

5.        单元的常见错误:(1)、单元接口;(2)、局部数据结构。(3)、独立路径。(4)、出错处理。(5)、边界条件。

6.        单元測试策略:有三种,独立的单元測试策略。自顶向下的单元測试策略和自底向上的单元測试策略。

独立的測试策略:不考虑每一个模块与其它模块之间的关系,为每一个模块设计桩模块和驱动模块。

每一个模块进行独立的单元測试。

自顶向下的測试策略:先对最顶层的单元进行測试,把顶层所调用的单元做成桩模块。其次对第二层进行測试,使用上面已測试的单元做驱动模块。

如此类推直到測试全然部模块。

自底向上測试:先对模块调用层次图上最低层的模块进行单元測试,模拟调用该模块的模块做驱动模块。然后再对上面一层做单元測试。用下面已被測试过的模块做桩模块。依次类推,直到測试全然部模块。

7.        单元測试过程:计划(測什么)、设计(測试方案、策略)、实现(写測试用例、代码)、运行(測试报告)四个阶段。

8.        单元測试的原则:(1)、对全新的代码或改动过的代码进行单元測试;(2)、单元測试依据单元測试计划和方案进行,排除測试的任意性。(3)、必须保证单元測试计划、单元測试方案、单元測试用例等经过评审;(4)、当測试用例的測试结果与预期结果不一致时。单元測试的运行人员需如实记录实际的測试结果;(5)、仅仅有当測试计划中的结束标准达到时,单元測试才干结束;(6)、对被測试单元需达到的一定的代码覆盖率要求。

三.  測试用例

1.        简单介绍:測试用例(Test Case)是为某个特殊目标而编制的一组測试输入、运行条件以及预期结果,以便測试某个程序路径或核实是否满足某个特定需求。也指对一项特定的软件产品进行測试任务的描写叙述,体现測试方案、方法、技术和策略。内容包含測试目标、測试环境、输入数据、測试步骤、预期结果、測试脚本等。并形成文档。

不同类别的软件,測试用例是不同的。

2.        概述:測试用例构成了设计和制定測试过程的基础。測试的“深度”与測试用例的数量成比例。因为每一个測试用例反映不同的场景、条件或经由产品的事件流。因而。随着測试用例数量的添加,你对产品质量和測试流程也就越有信心。

推断測试是否全然的一个主要评測方法是基于需求的覆盖,而这又是以确定、实施和/或运行的測试用例的数量为依据的。

測试工作量与測试用例的数量成比例。最佳方案是为每一个測试需求至少编制两个測试用例。一个測试用例用于证明该需求已经满足,通常称作正面測试用例。

还有一个測试用例反映某个无法接受、反常或意外的条件或数据,用于论证仅仅有在所需条件下才干够满足该需求。这个測试用例称作负面測试用例。

3.      设计方法:

(1)、白盒技术:白盒測试是结构測试,所以被測对象基本上是源程序。以程序的内部逻辑为基础设计測试用例

白盒測试的測试用例设计:一般採用逻辑覆盖法基本路径法进行设计。

逻辑覆盖是以程序内部的逻辑结构为基础的測试用例设计技术,这一方法要求測试人员对程序的逻辑结构有清晰的了解。

逻辑覆盖可分为:语句覆盖、判定覆盖、条件覆盖、判定-条件覆盖、条件组合覆盖与路径覆盖。

语句覆盖:在測试时,首先设计若干个測试用例,然后运行被測程序。使程序中的每一个可运行语句至少运行一次。

判定覆盖法:在測试时,首先设计若干个測试用例,然后运行被測程序,使得程序中的每一个推断的取真分支和取假分支至少经历一次,即推断的真假值均曾被满足。

条件覆盖法:在測试时。首先设计若干个測试用例,然后运行被測程序,要使每一个推断中每一个条件的可能取值至少满足一次。

判定条件覆盖法:在測试时,首先设计若干个測试用例,然后运行被測程序,使得推断中每一个条件的全部可能至少出现一次。并且每一个推断本身的判定结果至少出现一次。

路径覆盖法:在測试时,首先设计若干个測试用例,然后运行被測程序。要求覆盖程序中全部可能的路径。

基本路径覆盖法:是在程序控制流图的基础上,通过分析控制结构的环路复杂性,导出基本可运行路径集合。设计測试用例的方法。该方法把覆盖的路径数压缩到一定限度内,程序中的循环体最多仅仅运行一次。设计出的測试用例要保证在測试中,程序的每一个可运行语句至少运行一次。

循环路径測试:基本路径覆盖法将循环限制在最多一次,这样虽然大大减少了须要覆盖的路径的条数,但对循环的測试却不充分了,因此还须要对循环路径进行測试。循环路径測试包含。简单循环的測试和嵌套循环的測试。

每一种覆盖方法都有其优缺点。通常在设计測试用例时应该依据代码模块的复杂度,选择覆盖方法。

一般的代码的复杂度与測试用例设计的复杂度成正比。因此,设计人员必须做到模块或方法功能的单一性、高内聚性,使得方法或函数代码尽可能的简单;这样将可大大提高測试用例设计的easy度,提高測试用例的覆盖程度。

基本路径測试法是在程序控制流图的基础上。通过分析控制构造的环路复杂性,导出基本可运行路径集合。从而设计測试用例的方法。设计出的測试用例要保证在測试中程序的每一个可运行语句至少运行一次。

基本路径測试法包含下面5个方面:(1)、程序的控制流图:描写叙述程序控制流的一种图示方法。(2)、程序环境复杂性:McCabe复杂性度量;从程序的环路复杂性可导出程序基本路径集合中的独立路径条数,这是确定程序中每一个可运行语句至少运行依次所必须的測试用例数目的上界;(3)、导出測试用例;(4)、准备測试用例,确保基本路径集中的每一条路径的运行;(5)、图形矩阵:是在基本路径測试中起辅助作用的软件工具。利用它能够实现自己主动地确定一个基本路径集。

另外,对于測试用例的选择除了满足所选择的覆盖程度(或覆盖标准)外还须要尽可能的採用边界值分析法、错误猜測法等经常使用地设计方法。採用边界值分析法设计合理的输入条件与不合理的输入条件。条件边界測试用例应该包含输入參数的边界与条件边界(if,while。for,switch ,SQL Where子句等)。错误猜測法。列举出程序中全部可能的错误和easy错误发生的特殊情况,依据它们选择測试用例;在编码、单元測试阶段能够发现非常多常见的错误和疑似错误,对于这些错误应该作重点測试,并设计对应的測试用例。

(2)、黑盒技术:等价划分类、边界值分析、错误猜測、因果图、综合策略

4.        測试类设计:一个模块或一个方法(Method)并非一个独立的程序。在考虑測试它时要同一时候考虑它和外界的联系,用些辅助模块去模拟与所測模块相联系的其它模块。这些辅助模块分为两种:

(1)、驱动模块(driver):相当于所測模块的主程序。它接收測试数据,把这些数据传送给所測模块,最后再输出实际測试结果;

(2)、桩模块(stub):用于取代所測模块调用的子模块。桩模块能够做少量的数据操作。不须要把子模块全部功能都带进来,但不容许什么事情也不做。

打桩:一般在做单元或集成測试时,假设某个程序单元的某条语句。须要调用的一个外部函数还没有设计、编码、调试完毕的话,能够仅仅让它简单地返回几个支持測试用例的值就能够了,这样的状态的外部函数一般就叫做“打桩”。

所測模块与它相关的驱动模块及桩模块共同构成了一个“測试环境”。

驱动模块和桩模块的编写会给測试带来额外的开销。

因为它们在软件交付时并不作为产品的一部分一同交付,并且它们的编写须要一定的工作量。

特别是桩模块。不能仅仅简单地给出“以前进入”的信息。为了能够正确的測试软件。桩模块可能须要模拟实际子模块的功能。这样桩模块的建立就不是非常轻松了。

编写桩模块是困难费时的,事实上也是全然能够避免编写桩模块的。仅仅需在项目进度管理时将实际桩模块的代码编写工作安排在被測模块前编写就可以。并且这样能够提高測试工作的效率。提高实际桩模块的測试频率从而更有效的保证产品的质量。

可是,为了保证能够向上一层级提供稳定可靠的实际桩模块,为兴许模块測试打下良好的基础,驱动模块还是不可缺少的。

对于每一个包或子系统我们能够依据所编写的測试用例来编写一个測试模块类来做驱动模块,用于測试包中全部的待測试模块。

而最好不要在每一个类中用一个測试函数的方法,来測试跟踪类中全部的方法。

这样的优点在于:(1)、能够同一时候測试包中全部的方法或模块。也能够方便的測试跟踪指定的模块或方法;(2)、能够联合使用全部測试用例对同一段代码运行測试。发现问题;(3)、便以回归測试,当某个模块作了改动之后。仅仅要运行測试类就能够运行全部被測的模块或方法。这样不但能够方便得检查、跟踪所改动的代码,并且能够检查出改动对包内相关模块或方法所造成的影响。使改动引进的错误得以及时发现;(4)、复用測试方法,使測试单元保持持久性,并能够用既有的測试来编写相关測试;(5)、将測试代码与产品代码分开,使代码更清晰、简洁;提高測试代码与被測代码的可维护性。

5.        跟踪调试:跟踪调试不可是深入測试代码的最佳方法,并且也是程序调试发现错误根源的有利工具。測试类设计完毕后,最好能借助代码排错工具来跟踪调试待測代码段以深入的检查代码的逻辑错误。现有的代码开发工具(如:JBuilder)一般都集成了这类排错工具。排错工具一般由运行控制程序、运行状态查询程序、跟踪程序组成。运行控制程序包含断点定义、断点撤销、单步运行、断点运行、条件运行等功能。

运行状态查询程序包含寄存器、堆栈状态、变量、代码等与程序相关的各种状态信息的查询。跟踪程序用以跟踪程序运行过程中所经历的事件序列(如:分支、子程序调用等)。程序猿可通过对程序运行过程中各种状态的判别进行程序错误的识别、定位及改正。

对于模块的单元跟踪调试最好能够做到:每次改动被測模块后,都将全部測试用例跟踪运行一遍以排除全部可能出现或引进的错误。

在时间有限的情况下也必须调用驱动模块对全部的測试用例运行一次,并对出现错误或异常的測试用例跟踪运行一次。以发现问题的根源。

排错过程往往是一个艰苦的过程,特别是那种算法复杂、调用子模块较多的模块,对于错误的定位来说并非件easy的事情。虽然排错不是一门好学的技术(有时人们更愿意称之为艺术)。但还是有若干行之有效的方法和策略,下面介绍几种排错时应该採用的方法策略:(1)、断点设置。设置断点对源程序实行断点跟踪将能够大大提高排错的效率。通常断点的设置除了依据经验与错误信息来设置外,还应重点考虑下面几种类行的语句:A、函数调用语句。

子函数的调用语句是測试的重点,一方面因为在调用子函数时可能引起接口引用错误。还有一方面可能是子函数本身的错误。B、判定转移/循环语句。判定语句经常会因为边界值与比較优先级等问题引起错误或失效而作出错误的转移。因此。对于判定转移/循环语句也是一个重要的測试点;C、SQL语句。对于数据库的应用程序来说,SQL语句经常会在模块中占比較重要的业务逻辑,并且比較复杂。因此。它也属于比較easy出现错误的语句;D、复杂算法段。出错的概率常与算法的复杂度成正比。

所以越复杂的算法越须要作重点跟踪。如递归、回朔等算法。(2)、可疑变量查看,在跟踪运行状态下当程序停止在某条语句时可查看变量的当前值和对象的当前属性。通过对照这些变量当前值与预期值能够轻松的定位程序问题根源;(3)、SQL语句运行检查,在跟踪运行或运行状态下将疑似错误的SQL语句打印出来,又一次在数据库SQL查询分析器(如:Oracle SQL Plus)中跟踪运行能够较高效的检查纠正SQL语句错误;(4)、注意群集现象,经验表明測试后程序中残存的错误数目与该程序中已发现的错误数目或检错率成正比。依据这个规律,应当对错误群集的程序段进行重点測试。以提高測试投资的效益。假设发现某一代码段似乎比其它程序模块很多其它的错误倾向时,则应当花费较多的时间和代价測试这个程序模块。

6.        測试用例设计的基本原则:(1)、一个好的測试用例在于能够发现至今没有发现的错误。(2)、測试用例应由測试输入数据和与之对应的预期输出结果这两部分组成;(3)、在測试用例设计时。应当包含合理的输入条件和不合理的输入条件。

7.      測试用例的具体做法:

(1)、測试用例文档:编写測试用例文档应有文档模板,须符合内部的规范要求。

(2)、測试用例的设置:按功能设置用例、按路径设置用例、按功能、路径混合模式设置用例。

(3)、设计測试用例:測试用例能够分为基本事件、备选事件和异常事件。

四.   白盒測试

1.      白盒測试一般包含下面几项:

(1)、目的:保证程序创建的类与接口的完整与正确,以及程序模块单独正常运行。

保证局部模块功能完备性,运行正确性与稳定性。

         (2)、測试项:所要測试的类。

         (3)、測试依据:A、需求规格说明书、用例描写叙述清单;B、设计文档;C、编码规范;D、开发命名标准。

         (4)、通过的准则:创建的类、接口、方法、属性应与《设计文档》保持一致;程序的各种命名、凝视、代码行的格式等应符合《程序开发命名标准》和《编码规范》;程序模块能独立稳定运行。

         (5)、測试环境配置:A、測试工具;B、软件环境。

2.      測试步骤:

(1)、配置好測试环境;

(2)、编写測试用例;

(3)、静态測试、走查代码。

(4)、动态測试;

(5)、确定问题属性:分为四类,错误、缺陷、失效、故障。

错误是指计算值、观測值、測量值之间。或条件与真值之间,不符合规定的或理论上的正确值或条件。

缺陷是指与期望值或特征值的偏差。

故障是指功能部件不能运行所要求的功能。故障可能由错误、缺陷或失效引起。

失效是指功能部件运行其功能的能力丧失。系统或系统部件丧失了在规定限度内运行所要求功能的能力。

(6)、确定问题类别。

(7)、填写測试报告。

3.        白盒測试和单元測试的差别:(1)、測试目的:一个是測试程序的总体逻辑,还有一个是測试程序中一个独立的模块。(2)、通常的运行人员不一样:白盒一般由专门的白盒測试人员完毕,单元測试一般由程序猿自己完毕。


例程:

#include "gtest/gtest.h"
#include <iostream>

using namespace std;

int fun(int a, int b, int x)
{
	if (a > 1 && b == 0) {
		x =  x / a;
	} else {

	}

	if (a == 2 || x > 1) {
		x = x + 1;
	} else {

	}

	return x;
}

//语句覆盖法:使程序中的每一个可运行语句至少运行一次
TEST(TestCase1, case1)
{
	EXPECT_EQ(2, fun(2, 0, 3));//good
	EXPECT_EQ(4, fun(2, 1, 3));//bad
}

//判定覆盖法:使得程序中的每一个推断的取真分支和取假分支至少经历一次。即推断的真假值均曾被满足
TEST(TestCase1, case2)
{
	EXPECT_EQ(4, fun(1, 0, 3));
	EXPECT_EQ(1, fun(1, 0, 1));
}

//条件覆盖法:要使每一个推断中每一个条件的可能取值至少满足一次
TEST(TestCase1, case3)
{
	EXPECT_EQ(2, fun(2, 0, 3));
	EXPECT_EQ(1, fun(1, 0, 1));
	EXPECT_EQ(2, fun(2, 1, 1));
}

//判定条件覆盖法:使得推断中每一个条件的全部可能至少出现一次,并且每一个推断本身的判定结果至少出现一次
TEST(TestCase1, case4)
{
	EXPECT_EQ(2, fun(2, 0, 3));
	EXPECT_EQ(2, fun(2, 1, 1));
	EXPECT_EQ(4, fun(1, 0, 3));
	EXPECT_EQ(1, fun(1, 1, 1));
}

//路径覆盖法:要求覆盖程序中全部可能的路径
TEST(TestCase1, case5)
{
	EXPECT_EQ(2, fun(2, 0, 3));
	EXPECT_EQ(1, fun(1, 0, 1));
	EXPECT_EQ(2, fun(2, 1, 1));
	EXPECT_EQ(0, fun(3, 0, 1));
}


int main(int argc, char **argv)
{
	::testing::InitGoogleTest(&argc, argv);
	return RUN_ALL_TESTS();

	cout<<"ok!"<<endl;

	return 0;
}