posts - 21,  comments - 20,  trackbacks - 0
 

标准库<cstdlib>(被包含于<iostream>中)提供两个帮助生成伪随机数的函数:

函数一:int rand(void);
从srand (seed)中指定的seed开始,返回一个[seed, RAND_MAX(0x7fff))间的随机整数。

函数二:void srand(unsigned seed);
参数seed是rand()的种子,用来初始化rand()的起始值。

可以认为rand()在每次被调用的时候,它会查看:
1) 如果用户在此之前调用过srand(seed),给seed指定了一个值,那么它会自动调用
srand(seed)一次来初始化它的起始值。

2) 如果用户在此之前没有调用过srand(seed),它会自动调用srand(1)一次。

根据上面的第一点我们可以得出:
1) 如果希望rand()在每次程序运行时产生的值都不一样,必须给srand(seed)中的seed一个变值,这个变值必须在每次程序运行时都不一样(比如到目前为止流逝的时间)。
2) 否则,如果给seed指定的是一个定值,那么每次程序运行时rand()产生的值都会一样,虽然这个值会是[seed, RAND_MAX(0x7fff))之间的一个随机取得的值。
3) 如果在调用rand()之前没有调用过srand(seed),效果将和调用了srand(1)再调用rand()一样(1也是一个定值)。

举几个例子,假设我们要取得0~6之间的随机整数(不含6本身):

例一,不指定seed:
for(int i=0;i<10;i++){
ran_num=rand() % 6;
cout<<ran_num<<" ";
}
每次运行都将输出:5 5 4 4 5 4 0 0 4 2

例二,指定seed为定值1:
srand(1);
for(int i=0;i<10;i++){
ran_num=rand() % 6;
cout<<ran_num<<" ";
}
每次运行都将输出:5 5 4 4 5 4 0 0 4 2
跟例子一的结果完全一样。

例三,指定seed为定值6:
srand(6);
for(int i=0;i<10;i++){
ran_num=rand() % 6;
cout<<ran_num<<" ";

}
每次运行都将输出:4 1 5 1 4 3 4 4 2 2
随机值也是在[0,6)之间,随得的值跟srand(1)不同,但是每次运行的结果都相同。

例四,指定seed为当前系统流逝了的时间(单位为秒):time_t time(0):
#include <ctime>
//…
srand((unsigned)time(0));
for(int i=0;i<10;i++){
ran_num=rand() % 6;
cout<<ran_num<<" ";

}
第一次运行时输出:0 1 5 4 5 0 2 3 4 2
第二次:3 2 3 0 3 5 5 2 2 3
总之,每次运行结果将不一样,因为每次启动程序的时刻都不同(间隔须大于1秒?见下)。

关于time_t time(0):

time_t被定义为长整型,它返回从1970年1月1日零时零分零秒到目前为止所经过的时间,单位为秒。比如假设输出:
cout<<time(0);
值约为1169174701,约等于37(年)乘365(天)乘24(小时)乘3600(秒)(月日没算)。

另外,关于ran_num = rand() % 6,

将rand()的返回值与6求模是必须的,这样才能确保目的随机数落在[0,6)之间,否则rand()的返回值本身可能是很巨大的。
一个通用的公式是:
要取得[a,b)之间的随机整数,使用(rand() % (b-a))+ a (结果值将含a不含b)。
在a为0的情况下,简写为rand() % b。

最后,关于伪随机浮点数:

用rand() / double(RAND_MAX)可以取得0~1之间的浮点数(注意,不同于整型时候的公式,是除以,不是求模),举例:
double ran_numf=0.0;
srand((unsigned)time(0));
for(int i=0;i<10;i++){
ran_numf = rand() / (double)(RAND_MAX);
cout<<ran_numf<<" ";
}
运行结果为:0.716636,0.457725,…等10个0~1之间的浮点数,每次结果都不同。

如果想取更大范围的随机浮点数,比如1~10,可以将
rand() /(double)(RAND_MAX) 改为 rand() /(double)(RAND_MAX/10)
运行结果为:7.19362,6.45775,…等10个1~10之间的浮点数,每次结果都不同。
至于100,1000的情况,如此类推。

以上不是伪随机浮点数最好的实现方法,不过可以将就着用用…

本文转自赤龙发表的同名文

posted @ 2008-08-16 11:53 Niino 阅读(51553) | 评论 (6)编辑 收藏

定义完一个class一定要加;啊~~

posted @ 2008-08-06 14:17 Niino 阅读(122) | 评论 (0)编辑 收藏
struct stu
{
char a;
char b;
double c;
}

问sizeof(stu)=?
答案16

111->1+1+1
114->(1+1)+(1+1)+4
118->(1+3)+(1+3)+8
141->(1+3)+4+(1+3)
144->(1+3)+4+4
148->(1+3)+4+8
181->(1+7)+8+(1+7)
188->(1+7)+8+8
411->4+(1+1)+(1+1)
414->4+(1+3)+4
418->4+(1+3)+8
441->4+4+(1+3)
444->4+4+4
448->4+4+8
481->(4+4)+8+(1+7)
484->(4+4)+8+(4+4)
488->(4+4)+8+8
811->8+(1+3)+(1+3)
814->8+(1+3)+4
818->8+(1+7)+8
841->8+4+(1+3)
844->8+4+4
848->8+(4+4)+8
881->8+8+(1+7)
884->8+8+(4+4)
888->8+8+8

不同的编译器下会产生不同的情况,以上在VS2005和DVE-CPP上已验证
有空补空,没空对齐



posted @ 2008-07-23 18:59 Niino 阅读(251) | 评论 (0)编辑 收藏
#include "stdafx.h"
#include 
<iostream>
#include 
<fstream>
#include 
<iterator>
#include 
<algorithm>
#include 
<vector>
#include 
<string>

using namespace std;

int main()
{
    ifstream in_file( 
"input_file.txt" );
    ofstream out_file( 
"output_file.txt" );
    
    
if ( ! in_file || ! out_file )
    
{
         cerr
<<"!!unable to open the necessary files.\n";
         
return -1;
    }

    
    istream_iterator
< string > is( in_file );
    istream_iterator
< string > eof;
    
    vector 
< string > text;
    copy( 
is, eof, back_inserter( text ));
    
    sort( text.begin(), text.end() );
    
    ostream_iterator
< string > os( out_file," " );
    copy( text.begin(), text.end(), os );
}
代码如上,是<<Essential C++>>中的一段
input_file的内容为"7 956 1 5 147 3 78 24 87 62 45 12 77 86 64 52 58 74"
执行后
output_file中内容为"1 12 147 24 3 45 5 52 58 62 64 7 74 77 78 86 87 956"

- -
sort()对字符串排序...shit...只认首字符
posted @ 2008-07-19 12:36 Niino 阅读(166) | 评论 (0)编辑 收藏
调查的主题是在各位学习和使用C++或其他编程语言时,通常每天在电脑前花的时间:
1.两个小时以下
2.两到四个小时
3.四到八个小时
4.八小时以上

欢迎大家发言
posted @ 2008-07-19 11:03 Niino 阅读(882) | 评论 (6)编辑 收藏
角色扮演游戏引擎的设计原理--转自MOVE2008 角色扮演游戏引擎的设计原理   
      角色扮演游戏(RPG)是深受广大游戏迷们喜爱的一种游戏, 它以独特的互动性和故事性吸引了无数的玩家。它向人们提供了超出现实生活的广阔的虚拟世界,使人们能够尝试扮演不同的角色,去经历和体验各种不同的人生旅程或奇幻经历。这些体验都是在现实生活中无法实现的。在玩过许多游戏后,许多玩家都不再仅仅满足于一个游戏玩家的身份,而会思考游戏是如何制作的,并且打算制作一个自己的游戏,网上的各种游戏制作小组更是如雨后春笋般涌现。下面我就给大家介绍一下角色扮演游戏引擎的原理与制作,希望能对游戏制作爱好者有所帮助。 一 游戏引擎的原理   说到引擎,游戏迷们都很熟悉。游戏引擎是一个为运行某一类游戏的机器设计的能够被机器识别的代码(指令)集合。它象一个发动机,控制着游戏的运行。一个游戏作品可以分为游戏引擎和游戏资源两大部分。游戏资源包括图象,声音,动画等部分,列一个公式就是:游戏=引擎(程序代码)+资源(图象,声音,动画等)。游戏引擎则是按游戏设计的要求顺序的调用这些资源。 二 角色扮演游戏的制作   一个完整的角色扮演游戏的制作从大的分工来说可以分为:策划,程序设计,美工,音乐制作以及项目管理,后期的测试等。   策划主要任务是设计游戏的剧情,类型以及模式等,并分析游戏的复杂性有多大,内容有多少,策划的进度要多快等因素。   程序设计的任务是用某种编程语言来完成游戏的设计,并与策划配合,达到预期的目的。   美工主要是根据游戏的时代背景与主题设计游戏的场景及各种角色的图象。   音乐制作是根据游戏的剧情和背景制作游戏的音乐与音效。   项目管理主要是控制游戏制作的进程,充分利用现有的资源(人员,资金,设备等),以达到用尽量少的资金实现最大的收益。   后期的测试也是非常重要的一个环节,对于一个几十人花费几个月甚至是几年时间制作的游戏,测试往往能找到许多问题,只有改进程序才能确保游戏的安全发行。   由于文章主要是讲解游戏程序的制作的,所以策划,美工,音乐制作等方面请读者参考其它文章,下面我就讲讲游戏程序的设计。 (一) 开发工具与主要技术 1.件开发工具   游戏程序开发工具有很多,在不同游戏平台上有不同的开发工具。在个人计算机上,可以用目前流性的软件开发工具,比如:C,C++,VC++,Delphi,C++ Builder等。由于Windows操作系统的普及和其强大的多媒体功能,越来越多的游戏支持Windows操作系统。由于VC是微软的产品,用它来编写Windows程序有强大的程序接口和丰富的开发资源的支持,加之VC严谨的内存管理,在堆栈上良好的分配处理,生成代码的体积小,稳定性高的优点,所以VC++就成为目前游戏的主流开发工具。 2.DirectX组件的知识   谈到Windows系统下的游戏开发,我们就要说一下微软的DirectX SDK。   Windows系统有一个主要优点是应用程序和设备之间的独立性。然而应用程序的设备无关性是通过牺牲部分速度和效率的到的,Windows在硬件和软件间添加了中间抽象层,通过这些中间层我们的应用程序才能在不同的硬件上游刃有余。但是,我们因此而不能完全利用硬件的特征来获取最大限度的运算和显示速度。这一点在编写Windows游戏时是致命的,DirectX便是为解决这个问题而设计的。DirectX由快速的底层库组成并且没有给游戏设计添加过多的约束。微软的DirectX软件开发工具包(SDK)提供了一套优秀的应用程序编程接口(APIs),这个编程接口可以提供给你开发高质量、实时的应用程序所需要的各种资源。   DirectX的6个组件分别是:     DirectDraw: 使用页面切换的方法实现动画,它不仅可以访问系统内存,还可以访问显示内存。     Direct3D: 提供了3D硬件接口。     DirectSound: 立体声和3D声音效果,同时管理声卡的内存。     DirectPlay: 支持开发多人网络游戏,并能处理游戏中网络之间的通信问题。     DirectInput: 为大量的设备提供输入支持。     DirectSetup: 自动安装DirectX驱动程序。   随着DirectX版本的提高,还增加了音乐播放的DirectMusic。 3.AlphaBlend 技术   现在许多游戏为了达到光影或图象的透明效果都会采用AlphaBlend 技术。所谓AlphaBlend技术,其实就是按照"Alpha"混合向量的值来混合源像素和目标像素,一般用来处理半透明效果。在计算机中的图象可以用R(红色),G(绿色),B(蓝色)三原色来表示。假设一幅图象是A,另一幅透明的图象是B,那么透过B去看A,看上去的图象C就是B和A的混合图象,设B图象的透明度为alpha(取值为0-1,0为完全透明,1为完全不透明),Alpha混合公式如下:     R(C)=alpha*R(B)+(1-alpha)*R(A)     G(C)=alpha*G(B)+(1-alpha)*G(A)     B(C)=alpha*B(B)+(1-alpha)*B(A)   R(x)、G(x)、B(x)分别指颜色x的RGB分量原色值。从上面的公式可以知道,Alpha其实是一个决定混合透明度的数值。应用Alpha混合技术,可以实现游戏中的许多特效,比如火光、烟雾、阴影、动态光源等半透明效果。 4.A*算法   在许多游戏中要用鼠标控制人物运动,而且让人物从目前的位置走到目标位置应该走最短的路径。这就要用到最短路径搜索算法即A*算法了。   A*算法实际是一种启发式搜索,所谓启发式搜索,就是利用一个估价函数评估每次的的决策的价值,决定先尝试哪一种方案。如果一个估价函数可以找出最短的路径,我们称之为可采纳性。A*算法是一个可采纳的最好优先算法。A*算法的估价函数可表示为:     f(n) = g(n) + h(n)   这里,f(n)是节点n的估价函数,g(n)是起点到终点的最短路径值,h(n)是n到目标的最断路经的启发值。由于A*算法比较复杂,限于篇幅,在此简单介绍一下,具体理论朋友们可以看人工智能方面的书籍了解详细的情况。   其它技术还有粒子系统,音频与视频的调用,图象文件的格式与信息存储等,大家可以在学好DirectX的基础上逐渐学习更多的技术。 (二)游戏的具体制作 1.地图编辑器的制作   RPG游戏往往要有大量的场景,场景中根据需要可以有草地,湖泊,树木,房屋,家具等道俱,由于一个游戏需要很多场景且地图越来越大,为了节省空间,提高图象文件的可重用性,RPG游戏的画面采用很多重复的单元(可以叫做“图块”)所构成的,这就要用到地图编辑器了。我们在制作游戏引擎前,要完成地图编辑器的制作。在 RPG游戏里,场景的构成,是图块排列顺序的记录。首先制定一个场景构成文件的格式,在这个文件里记录构成场景所需要的图块的排列顺序,因为我们已经为每个图块建立了索引,所以只需要记录这些索引就可以了。一个场景的构成,是分成几层来完成的:地面,建筑和植物,家具摆设,和在场景中活动的人物或者物体(比如飘扬的旗帜),按照一定的顺序把它们依次显示到屏幕上,就形成了一个丰富多采的场景。我们可以用数组来表示地图场景的生成过程。
MapData[X][Y]; //地图数据,X表示地图宽度,Y表示地图高度 
Picture[num]; //道具的图片,num表示道具的总数 void MakeBackGround() //生成场景函数 
{  
    
int n;  
    
forint i=0; i<Y; i++//共Y行  
       forint j=0; j<X; j++//共X列  
         {   
            n
=MapData[ i ][ j ]; //取得该位置的道具编号   
            Draw( j*32, i*32, Picture[n]); //在此位置(j*32,i*32)画道具  
          }
 
}
 
2.游戏的模块的划分   游戏按功能分为:消息处理系统、场景显示及行走系统、打斗系统三大主要部分。其中又以消息处理系统为核心模块,其余部分紧紧围绕它运行。 一:消息处理系统   消息处理系统是游戏的核心部分。游戏用到的消息处理系统先等待消息,然后根据收到的消息转到相应的函数进行处理。比如:主角碰到敌人后,我们就让程序产生‘打斗消息’,消息处理系统收到这个消息后就会马上转到打斗模块中去。消息处理的大体框架如下:
//定义程序中要用到的变量 
DWORD Message; //消息变量 
WinMain() //进入程序 
{  
    初始化主窗口;  
    初始化DirectDraw环境,并调入程序需要的图形、地图数据;  
    
while1 ) //消息循环  
    {   
        
switch( Message )   
           
{    
              
case 行走消息: 行走模块();                case 打斗消息:打斗模块();                case 事件消息: 事件模块();   
            }
  
     }
 
}
二:场景显示及行走系统   作为RPG游戏,其所有事件的发生几乎都是和场景有关,例如:不同的地方会碰到不同的敌人、与不同的人对话得知不同的事情等。鉴于这部分的重要性,我们可再将它划分为:背景显示、行走 和 事件发生 三个子模块,分别处理各自的功能。下面进行具体分析。 (一)背景显示   程序运行后,先读取前面地图编辑器制作的场景所需要的图块的排列顺序,按照排列顺序将图象拼成一个完整的场景,一般做法是:在内存中开辟一到两个屏幕缓存区,事先把即将显示的图象数据准备在缓存区内,然后一次性搬家:把它们传送到真正的屏幕缓冲区内。   游戏用到的图片则事先制作好并存于另外的图形文件中。地图编辑器制作的场景文件仅仅是对应的数据,而不是真正的图片。在游戏中生成场景就是地图编辑的逆过程,一个是根据场景生成数据,而另一个是根据数据生成场景。 (二)行走   要让主角在场景中行走,至少要有上、下、左、右四个行走方向,每个方向4幅图(站立、迈左腿、迈右腿、迈左腿),如图:游戏中一定要将图片的背景设为透明,这样在画人物的时候就不会覆盖上背景色了(这一技术DirectDraw中只要用SetColorKey()函数将原图片背景色过滤掉就行了)。我们让主角位置不动,而使场景移动,即采用滚屏技术来实现角色在场景上移动。这样角色一直保持在屏幕的正中间,需要做的工作只是根据行走方向和步伐不停变换图片而已。行走时的障碍物判断也是每一个场景中必定要有的,有一些道具如树木、房屋等是不可跨越的。对此我主要用一个二维数组来对应一个场景,每一个数组值代表场景的一小格(见图3)。有障碍的地方,该数组的对应值为1,可通过的地方的值为0。 (三)事件发生   事件发生原理就是把相应事件的序号存储在地图的某些格子中,当主角一踏入这个格子就会触发对应事件。例如:在游戏开始时,主角是在他的家里。他要是想出去的话,就需要执行场景切换这个处理函数。我们假定该事件的编号为001,那么在地图上把家门外路口处的格子值设为001。这样主角走到路口时,编号为001的场景切换函数就会被触发,于是主角便到了下一个场景中。程序具体如下:
void MessageLoop( int Msg ) //消息循环 
{
        
switch( Msg )  
         
{
               
char AddressName[16]; //数组AddressName[16]用来存储主角所在地点的名称   
               case ADDRESS == 001// 由ADDRESS的值决定场景值(出门)   
               ScreenX=12
             ScreenY
=0//初始化游戏背景位置                Hero.x=360; 
             Hero.y=80;//主角坐标   
               Move();//主角移动函数   
            
//以下程序用来显示主角所在地点   
               sprintf(AddressName,"下一幅游戏场景的名称");
             PrintText(lpDDSPrimary, 
280330,AddressName , RGB(255,255,255));//在屏幕上显示出场景的名称
              break;
         }

}
三:打斗系统   绝大多数的RPG都是有战斗存在的,因此,打斗系统就成为RPG系统中很重要的一环。有不少RPG游戏采用回合制打斗方式,因为实现起来较为简单。和打斗紧密相关的是升级,通常在一场战斗结束后,主角的经验值都会增加。而当经验值到达一定程度时,角色就升级了。   上面我简要的介绍了角色扮演游戏的制作,由于写这篇文章的目的是让读者对角色扮演游戏的制作有一个基本的了解,所以读者朋友们可以研究相关资料

posted @ 2008-07-18 18:10 Niino 阅读(123) | 评论 (0)编辑 收藏
 软件测试软件开发过程的重要组成部分,是用来确认一个程序的品质或性能是否符合开发之前所提出的一些要求。软件测试就是在软件投入运行前,对软件需求分析、设计规格说明和编码的最终复审,是软件质量保证的关键步骤。软件测试是为了发现错误而执行程序的过程。软件测试在软件生存期中横跨两个阶段:通常在编写出每一个模块之后就对它做必要的测试(称为单元测试)。编码和单元测试属于软件生存期中的同一个阶段。在结束这个阶段后对软件系统还要进行各种综合测试,这是软件生存期的另一个独立阶段,即测试阶段。

一、软件测试的目的

 

      软件测试的目的,第一是确认软件的质量,其一方面是确认软件做了你所期望的事情(Do the right thing),另一方面是确认软件以正确的方式来做了这个事件(Do it right)。

      第二是提供信息,比如提供给开发人员或程序经理的反馈信息,为风险评估所准备的信息。

      第三软件测试不仅是在测试软件产品的本身,而且还包括软件开发的过程。如果一个软件产品开发完成之后发现了很多问题,这说明此软件开发过程很可能是有缺陷的。因此软件测试的第三个目的是保证整个软件开发过程是高质量的。

      软件质量是由几个方面来衡量的:一、在正确的时间用正确的的方法把一个工作做正确(Doing the right things right at the right time.)。二、符合一些应用标准的要求,比如不同国家的用户不同的操作习惯和要求,项目工程中的可维护性、可测试性等要求。三、质量本身就是软件达到了最开始所设定的要求,而代码的优美或精巧的技巧并不代表软件的高质量(Quality is defined as conformance to requirements, not as “goodness” or “elegance”.)。四、质量也代表着它符合客户的需要(Quality also means “meet customer needs”.)。作为软件测试这个行业,最重要的一件事就是从客户的需求出发,从客户的角度去看产品,客户会怎么去使用这个产品,使用过程中会遇到什么样的问题。只有这些问题都解决了,软件产品的质量才可以说是上去了。

      测试人员在软件开发过程中的任务:

      1、寻找Bug;
      2、避免软件开发过程中的缺陷;
      3、衡量软件的品质;
      4、关注用户的需求。

      总的目标是:确保软件的质量。

二、软件测试的原则


      软件测试从不同的角度出发会派生出两种不同的测试原则,从用户的角度出发,就是希望通过软件测试能充分暴露软件中存在的问题和缺陷,从而考虑是否可以接受该产品,从开发者的角度出发,就是希望测试能表明软件产品不存在错误,已经正确地实现了用户的需求,确立人们对软件质量的信心。

      为了达到上述的原则,那么需要注意以下几点:
1.应当把“尽早和不断的测试”作为开发者的座右铭
2.程序员应该避免检查自己的程序,测试工作应该由独立的专业的软件测试机构来完。
3.设计测试用例时应该考虑到合法的输入和不合法的输入以及各种边界条件,特殊情况要制造极端状态和意外状态,比如网络异常中断、电源断电等情况。
4.一定要注意测试中的错误集中发生现象,这和程序员的编程水平和习惯有很大的关系。
5.对测试错误结果一定要有一个确认的过程,一般有A测试出来的错误,一定要有一个B来确认,严重的错误可以召开评审会进行讨论和分析。
6.制定严格的测试计划,并把测试时间安排的尽量宽松,不要希望在极短的时间内完成一个高水平的测试。
7.回归测试的关联性一定要引起充分的注意,修改一个错误而引起更多的错误出现的现象并不少见。
8.妥善保存一切测试过程文档,意义是不言而喻的,测试的重现性往往要靠测试文档。


三、软件测试的对象

  软件测试并不等于程序测试。软件测试应该贯穿整个软件定义与开发整个期间。因此需求分析、概要设计、详细设计以及程序编码等各阶段所得到的文档,包括需求规格说明、概要设计规格说明、详细设计规格说明以及源程序,都应该是软件测试的对象。

  在对需求理解与表达的正确性、设计与表达的正确性、实现的正确性以及运行的正确性的验证中,任何一个环节发生了问题都可能在软件测试中表现出来。 


四、软件测试方法
 
 
软件测试的基本方法
单元测试的基本方法
综合测试的基本方法
确认测试的基本方法
系统测试的基本方法
软件测试的基本方法

  软件测试的方法和技术是多种多样的。
  对于软件测试技术,可以从不同的角度加以分

  从是否需要执行被测软件的角度,可分为静态测试和动态测试。
  从测试是否针对系统的内部结构和具体实现算法的角度来看,可分为白盒测试黑盒测试

1、黑盒测试

  黑盒测试也称功能测试数据驱动测试,它是在已知产品所应具有的功能,通过测试来检测每个功能是否都能正常使用,在测试时,把程序看作一个不能打开的黑盆子,在完全不考虑程序内部结构和内部特性的情况下,测试者在程序接口进行测试,它只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数锯而产生正确的输出信息,并且保持外部信息(如数据库文件)的完整性。黑盒测试方法主要有等价类划分、边值分析、因果图、错误推测等,主要用于软件确认测试。 “黑盒”法着眼于程序外部结构、不考虑内部逻辑结构、针对软件界面和软件功能进行测试。“黑盒”法是穷举输入测试,只有把所有可能的输入都作为测试情况使用,才能以这种方法查出程序中所有的错误。实际上测试情况有无穷多个,人们不仅要测试所有合法的输入,而且还要对那些不合法但是可能的输入进行测试。

2、白盒测试

  白盒测试也称结构测试逻辑驱动测试,它是知道产品内部工作过程,可通过测试来检测产品内部动作是否按照规格说明书的规定正常进行,按照程序内部的结构测试程序,检验程序中的每条通路是否都有能按预定要求正确工作,而不顾它的功能,白盒测试的主要方法有逻辑驱动、基路测试等,主要用于软件验证。

  “白盒”法全面了解程序内部逻辑结构、对所有逻辑路径进行测试。“白盒”法是穷举路径测试。在使用这一方案时,测试者必须检查程序的内部结构,从检查程序的逻辑着手,得出测试数据。贯穿程序的独立路径数是天文数字。但即使每条路径都测试了仍然可能有错误。第一,穷举路径测试决不能查出程序违反了设计规范,即程序本身是个错误的程序。第二,穷举路径测试不可能查出程序中因遗漏路径而出错。第三,穷举路径测试可能发现不了一些与数据相关的错误。

3.ALAC(Act-like-a-customer)测试

 

  ALAC测试是一种基于客户使用产品的知识开发出来的测试方法。ALAC测试是基于复杂的软件产品有许多错误的原则。最大的受益者是用户,缺陷查找和改正将针对哪些客户最容易遇到的错误。

单元测试的基本方法

单元测试的对象是软件设计的最小单位模块。单元测试的依据是详细设描述,单元测试应对模块内所有重要的控制路径设计测试用例,以便发现模块内部的错误。单元测试多采用白盒测试技术,系统内多个模块可以并行地进行测试。
单元测试任务

  单元测试任务包括: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错误陈述中未能提供足够的定位出错信息。

  边界条件测试是单元测试中最后,也是最重要的一项任务。众的周知,软件经常在边界上失效,采用边界值分析技术,针对边界值及其左、右设计测试用例,很有可能发现新的错误。

单元测试过程

  一般认为单元测试应紧接在编码之后,当源程序编制完成并通过复审和编译检查,便可开始单元测试。测试用例的设计应与复审工作相结合,根据设计信息选取测试数据,将增大发现上述各类错误的可能性。在确定测试用例的同时,应给出期望结果。

  应为测试模块开发一个驱动模块(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) 为测试软件系统的输入信息设计出错处理通路;
   (2) 设计测试用例,模拟错误数据和软件界面可能发生的错误,记录测试结果,为系统测试提供经验和帮助;
   (3) 参与系统测试的规划和设计,保证软件测试的合理性。

  系统测试应该由若干个不同测试组成,目的是充分运行系统,验证系统各部件是否都能政党工作并完成所赋予的任务。下面简单讨论几类系统测试。

1、恢复测试

  恢复测试主要检查系统的容错能力。当系统出错时,能否在指定时间间隔内修正错误并重新启动系统。恢复测试首先要采用各种办法强迫系统失败,然后验证系统是否能尽快恢复。对于自动恢复需验证重新初始化(reinitialization)、检查点(checkpointing mechanisms)、数据恢复(data recovery)和重新启动 (restart)等机制的正确性;对于人工干预的恢复系统,还需估测平均修复时间,确定其是否在可接受的范围内。

2、安全测试

  安全测试检查系统对非法侵入的防范能力。安全测试期间,测试人员假扮非法入侵者,采用各种办法试图突破防线。例如,①想方设法截取或破译口令;②专门定做软件破坏系统的保护机制;③故意导致系统失败,企图趁恢复之机非法进入;④试图通过浏览非保密数据,推导所需信息,等等。理论上讲,只要有足够的时间和资源,没有不可进入的系统。因此系统安全设计的准则是,使非法侵入的代价超过被保护信息的价值。此时非法侵入者已无利可图。

3、强度测试

  强度测试检查程序对异常情况的抵抗能力。强度测试总是迫使系统在异常的资源配置下运行。例如,①当中断的正常频率为每秒一至两个时,运行每秒产生十个中断的测试用例;②定量地增长数据输入率,检查输入子功能的反映能力;③运行需要最大存储空间(或其他资源)的测试用例;④运行可能导致虚存操作系统崩溃或磁盘数据剧烈抖动的测试用例,等等。

4、 性能测试

  对于那些实时和嵌入式系统,软件部分即使满足功能要求,也未必能够满足性能要求,虽然从单元测试起,每一测试步骤都包含性能测试,但只有当系统真正集成之后,在真实环境中才能全面、可靠地测试运行性能系统性能测试是为了完成这一任务。性能测试有时与强度测试相结合,经常需要其他软硬件的配套支持。

五、软件测试的类型

常见的软件测试类型有:

      BVT (Build Verification Test)

      BVT是在所有开发工程师都已经检入自己的代码,项目组编译生成当天的版本之后进行,主要目的是验证最新生成的软件版本在功能上是否完整,主要的软件特性是否正确。如无大的问题,就可以进行相应的功能测试。BVT优点是时间短,验证了软件的基本功能。缺点是该种测试的覆盖率很低。因为运行时间短,不可能把所有的情况都测试到。

      Scenario Tests(基于用户实际应用场景的测试)

      在做BVT、功能测试的时候,可能测试主要集中在某个模块,或比较分离的功能上。当用户来使用这个应用程序的时候,各个模块是作为一个整体来使用的,那么在做测试的时候,就需要模仿用户这样一个真实的使用环境,即用户会有哪些用法,会用这个应用程序做哪些事情,操作会是一个怎样的流程。加了这些测试用例后,再与BVT、功能测试配合,就能使软件整体都能符合用户使用的要求。Scenario Tests优点是关注了用户的需求,缺点是有时候难以真正模仿用户真实的使用情况。

      Smoke Test

      在测试中发现问题,找到了一个Bug,然后开发人员会来修复这个Bug。这时想知道这次修复是否真的解决了程序的Bug,或者是否会对其它模块造成影响,就需要针对此问题进行专门测试,这个过程就被称为Smoke Test。在很多情况下,做Smoke Test是开发人员在试图解决一个问题的时候,造成了其它功能模块一系列的连锁反应,原因可能是只集中考虑了一开始的那个问题,而忽略其它的问题,这就可能引起了新的Bug。Smoke Test优点是节省测试时间,防止build失败。缺点是覆盖率还是比较低。

      此外,Application Compatibility Test(兼容性测试),主要目的是为了兼容第三方软件,确保第三方软件能正常运行,用户不受影响。Accessibility Test(软件适用性测试),是确保软件对于某些有残疾的人士也能正常的使用,但优先级比较低。其它的测试还有Functional Test(功能测试)、Security Test(安全性测试)、Stress Test(压力测试)、Performance Test(性能测试)、Regression Test(回归测试)、Setup/Upgrade Test(安装升级测试)等。

六、软件测试支持工具


      一些受软件开发人员欢迎的软件测试工具为软件测试提供了强有力的支持。本文将介绍美国Rational公司的著名套装软件SQA和Pure Atria公司极具特色的Purify。

      SQA SuiteSQA直接支持对客户/服务器应用软件的测试,它的一个重要特点是可以自动驱动被测程序的运行。SQA可以自动记录和重放程序执行过程,从而实现了对测试进行"复查"的自动化

      由于测试是一个需要反复进行的过程,常常要数十次甚至数百次地重复。因此,这一特性大大地提高了软件"再测试"(Re-Test)和"回归测试"(Regression)的自动化程度,把测试人员从繁杂的、重复性的手工测试中解脱出来,从而显著地提高软件测试效率。

      除了这个最基本的自动录放功能外,它还提供了一系列的辅助支持功能,比如,

  · 被录制的程序执行过程可以被自动转换成具有良好可读性的高级语言程序,从而使这个测试驱动程序可以由测试人员根据测试需要进行必要的修改,甚至完全用手工方式编制。

  ·自动记录和分析比较测试的执行结果。不论是简单的正文方式的输出结果,还是任意的图表、声音、动画、图形用户界面(GUI)中的任一构件,都可以根据测试人员的指定被自动记录在测试结果库中,并可对两次测试的结果自动地进行比较,指出其差异部分。此项功能无疑对"自动查找错误"很有帮助。

  ·调节和设定事件的发生时间和速度。

  ·基本的测试库管理功能。

  此外,SQA还支持软件测试人员进行以下工作:

  ·制定测试计划和测试大纲,并将这些文档按照自然的树状结构分层地管理起来,并据此控制和驱动整个测试过程。

  ·不仅能够自动记录各类测试结果,而且对其进行修改,从而使得测试人员可以在程序运行结果尚有许多错误的情况下,通过对所记录下的结果做适当修正来获得理想的"期望结果" ,为测试结果的自动比较奠定基础。

  ·测试问题报告的记录与管理。

      总之,SQA Suite提供了一个比较完整的测试平台,以支持软件测试的各种基本活动,包括测试计划与测试大纲的制定、回归测试的自动化、测试结果的分析比较、软件问题报告的生成与自动分发和控制等。对于许多应用软件的开发无疑是个有力的测试支持工具。

      Purify是原PureAtria公司(现已经与美国Rational公司合并,改名为美国Rational公司)于90年代初率先推出的专门用于检测程序中种种内存使用错误的软件工具。几乎所有使用过C语言开发软件的程序员都会有这样的体会,C语言中使用极为灵活的指针给程序员带来了很大便利,但同时也制造了许多的麻烦。由于指针使用不当而引起的错误通常是最难发现的,同时也是最难定位的一类错误。而Purify对多种常见的内存使用错误的检错能力和准确的定位,受到广大软件开发人员的青睐。

      Purify可以自动识别出二十多种内存使用错误,包括

  ·未初始化的局部变量

  ·未申请的内存

  ·使用已释放的内存

  ·数组越界

  ·内存丢失

  ·文件描述问题

  ·栈溢出问题

  ·栈结构边界错误等

      在下面的例子中,暗藏着两个内存使用错误。第一行为指针数组pp申请的空间尺寸不对。这类错误往往不易发现,因为在C语言中,一些"轻微"的内存越界可能被系统所容忍。但这往往是导致更严重错误的根源。例如,可能破坏其它数据区等。最后一行的错误是在释放pp 之前没有释放赋予它的字符串空间,从而把它们"丢失"了。这类错误犹如慢性自杀,它会逐渐消耗掉内存,降低系统的运行效率,直到完全崩溃。而真正的问题在于,这些程序中的"恶性肿瘤"用常规的测试手段和调试工具是极难发现和加以定位的。Purify则在此充分显示了它的强大功效,所到之处,即对所测试过的情况,上述各种常见的内存错误都可以被一一揭露出来,并且准确地指出错误的类型和位置。从而大大地提高了测试和纠错的效率,提高了软件的可靠性。

  …/"to get 10 words and print them out"/

  if(!(pp=(char**)malloc(10))){

  /*Size should be 10*sizeof(char*)*/

  printf("Out of memory.\n");

  exit(-1);

  }

  for(i=0;i<10;i++){

  scanf("%s",buffer);

  if(!(pp[i]=(char*)malloc(strlen(buffer)+1))){

  print("Out of Memory.\ n");

  exit(-1);

  }

  strcpy(pp[i],buffer);

  printf(pp[i]);

  }

  free(pp);/*all the strings pointed by it are lost!*/

  ………

      今年以来,原PureAtria公司陆续推出了其系列产品?/FONT>Pure,包括支持内存检测的Purify ,支持路径覆盖的PureCoverage,支持多线程应用程序性能测试的Quantify,以及用以提高测试期间连接编译被测程序效率的PureLink等。Pure系列现已支持C、C++、FORTRAN语言,以及UNIX和Window NT等操作系统,如Sun OS、Solaris 2.3,HP-UX,Windows NT Server以及IBM A/ X等。

      结束语

      充分认识软件测试的重要性和复杂性,合理地选择测试方法,有效地组织测试人员和安排测试任务,并且尽量使用软件测试工具增强软件测试的自动化程度,无疑可以帮助软件开发和测试人员大大提高测试效率和软件的质量。


七、软件测试的基本过程

      软件测试是一个极为复杂的过程。如图一所示,一个规范化的软件测试过程通常须包括以下基本的测试活动。

  ·拟定软件测试计划

  ·编制软件测试大纲

  ·设计和生成测试用例

  ·实施测试

  ·生成软件问题报告

      对整个测试过程进行有效的管理实际上,软件测试过程与整个软件开发过程基本上是平行进行的。测试计划早在需求分析阶段即应开始制定,其它相关工作,包括测试大纲的制定、测试数据的生成、测试工具的选择和开发等也应在测试阶段之前进行。充分的准备工作可以有效地克服测试的盲目性,缩短测试周期,提高测试效率,并且起到测试文档与开发文档互查的作用。

      此外,软件测试的实施阶段是由一系列的测试周期(Test Cycle)组成的。在每个测试周期中,软件测试工程师将依据预先编制好的测试大纲和准备好的测试用例,对被测软件进行完整的测试。测试与纠错通常是反复交替进行的。当使用专业测试人员时,测试与纠错甚至是平行进行的,从而压缩总的开发时间。更重要的是,由于专业测试人员丰富的测试经验、所采用的系统化的测试方法、全时的投入,特别是独立于开发人员的思维,使得他们能够更有效地发现许多单靠开发人员很难发现的错误和问题。

      软件测试大纲是软件测试的依据。它明确详尽地规定了在测试中针对系统的每一项功能或特性所必须完成的基本测试项目和测试完成的标准。无论是自动测试还是手动测试,都必须满足测试大纲的要求。

      一般而言,测试用例是指为实施一次测试而向被测系统提供的输入数据、操作或各种环境设置。测试用例控制着软件测试的执行过程,它是对测试大纲中每个测试项目的进一步实例化。已有许多著名的论著总结了设计测试用例的各种规则和策略。从工程实践的角度讲有几条基本准则:

  1.测试用例的代表性:能够代表各种合理和不合理的、合法的和非法的、边界和越界的,以及极限的输入数据、操作和环境设置等;

  2.测试结果的可判定性:即测试执行结果的正确性是可判定的或可评估的;

  3.测试结果的可再现性:即对同样的测试用例,系统的执行结果应当是相同的。

八、制定成功的测试计划


      “工欲善其事,必先利其器”。专业的测试必须以一个好的测试计划作为基础。尽管测试的每一个步骤都是独立的,但是必定要有一个起到框架结构作用的测试计划。测试的计划应该作为测试的起始步骤和重要环节。一个测试计划应包括:产品基本情况调研、测试需求说明、测试策略和记录、测试资源配置、计划表、问题跟踪报告、测试计划的评审、结果等等。

      产品基本情况调研:

      这部分应包括产品的一些基本情况介绍,例如:产品的运行平台和应用的领域,产品的特点和主要的功能模块,产品的特点等。对于大的测试项目,还要包括测试的目的和侧重点。

      具体的要点有:

      目的:重点描述如何使测试建立在客观的基础上,定义测试的策略,测试的配置, 粗略的估计测试大致需要的周期和最终测试报告递交的时间。

      变更:说明有可能会导致测试计划变更的事件。包括测试工具改进了,测试的环境改变了,或者是添加了新的功能。

      技术结构:可以借助画图,将要测试的软件划分成几个组成部分,规划成一个适用于测试的完整的系统,包括数据是如何存储的,如何传递的(数据流图),每一个部分的测试是要达到什么样的目的。每一个部分是怎么实现数据更新的。还有就是常规性的技术要求,比如运行平台、需要什么样的数据库等等。

      产品规格:就是制造商和产品版本号的说明。

      测试范围:简单的描述如何搭建测试平台以及测试的潜在的风险。

      项目信息:说明要测试的项目的相关资料,如:用户文档,产品描述,主要功能的举例说明。

      测试需求说明:

      这一部分要列出所有要测试的功能项。凡是没有出现在这个清单里的功能项都排除在测试的范围之外。万一有一天你在一个没有测试的部分里发现了一个问题,你应该很高兴你有这个记录在案的文档,可以证明你测了什么没测什么。具体要点有:

      功能的测试:理论上是测试是要覆盖所有的功能项,例如:在数据库中添加、编辑、删除记录等等,这会是一个浩大的工程,但是有利于测试的完整性。

      设计的测试:对于一些用户界面、菜单的结构还有窗体的设计是否合理等的测试。

      整体考虑:这部分测试需求要考虑到数据流从软件中的一个模块流到另一个模块的过程中的正确性。

      测试的策略和记录:

      这是整个测试计划的重点所在,要描述如何公正客观地开展测试,要考虑:模块、功能、整体、系统、版本、压力、性能、配置和安装等各个因素的影响。要尽可能的考虑到细节,越详细越好,并制作测试记录文档的模板,为即将开始的测试做准备,测试记录重要包括的部分具体说明如下:

      公正性声明:要对测试的公正性、遵照的标准做一个说明,证明测试是客观的,整体上,软件功能要满足需求,实现正确,和用户文档的描述保持一致。

      测试案例:描述测试案例是什么样的,采用了什么工具,工具的来源是什么,如何执行的,用了什么样的数据。测试的记录中要为将来的回归测试留有余地,当然,也要考虑同时安装的别的软件对正在测试的软件会造成的影响。

      特殊考虑:有的时候,针对一些外界环境的影响,要对软件进行一些特殊方面的测试。

      经验判断:对以往的测试中,经常出现的问题加以考虑。

      设想:采取一些发散性的思维,往往能帮助你找的测试的新途径。

      测试资源配置:

      项目资源计划:制定一个项目资源计划,包含的是每一个阶段的任务、所需要的资源,当发生类似到了使用期限或者资源共享的事情的时候,要更新这个计划。

      计划表:

      测试的计划表可以做成一个多个项目通用的形式,根据大致的时间估计来制作,操作流程要以软件测试的常规周期作为参考,也可以是根据什么时候应该测试哪一个模块来制定。

      问题跟踪报告:

      在测试的计划阶段,我们应该明确如何准备去做一个问题报告以及如何去界定一个问题的性质,问题报告要包括问题的发现者和修改者、问题发生的频率、用了什么样的测试案例测出该问题的,以及明确问题产生时的测试环境。

      问题描述尽可能是定量的,分门别类的列举,问题有几种:

  1、严重问题:严重问题意味着功能不可用,或者是权限限制方面的失误等等,也可能是某个地方的改变造成了别的地方的问题。

  2、一般问题:功能没有按设计要求实现或者是一些界面交互的实现不正确。

  3、建议问题:功能运行得不象要求的那么快,或者不符合某些约定俗成的习惯,但不影响系统的性能,界面先是错误,格式不对,含义模糊混淆的提示信息等等。

      测试计划的评审:

      又叫测试规范的评审,在测试真正实施开展之前必须要认真负责的检查一遍,获得整个测试部门人员的认同,包括部门的负责人的同意和签字。

      结果:

      计划并不是到这里就结束了,在最后测试结果的评审中,必须要严格验证计划和实际的执行是不是有偏差,体现在最终报告的内容是否和测试的计划保持一致,然后,就可以开始着手制作下一个测试计划了。


九、报告软件测试错误的规范  
 

      报告软件测试错误的目的是为了保证修复错误的人员可以重复报告的错误,从而有利于分析错误产生的原因,定位错误,然后修正之。因此,报告软件测试错误的基本要求是准确、简洁、完整、规范。需要掌握的报告技术归纳如下。
   1. 描述 (Descrīption),简洁、准确,完整,揭示错误实质,记录缺陷或错误出现的位置

  描述要准确反映错误的本质内容,简短明了。为了便于在软件错误管理数据库中寻找制定的测试错误,包含错误发生时的用户界面(UI)是个良好的习惯。例如记录对话框的标题、菜单、按钮等控件的名称。

  2. 明确指明错误类型:布局、翻译、功能、双字节
根据错误的现象,总结判断错误的类型。例如,即布局错误、翻译错误、功能错误、双字节错误,这是最常见的缺陷或错误类型,其他形式的缺陷或错误也从属于其中某种形式。

  3. 短行之间使用自动数字序号,使用相同的字体、字号、行间距

  短行之间使用自动数字序号,使用相同的字体、字号、行间距,可以保证各条记录格式一致,做到规范专业。

  4. UI要加引号,可以单引号,推荐使用双引号

  UI加引号,可以容易区分UI与普通文本,便于分辨、定位缺陷或错误。

  5. 每一个步骤尽量只记录一个操作

  保证简洁、条理井然,容易重复操作步骤。

  6. 确认步骤完整,准确,简短

  保证快速准确的重复错误,“完整”即没有缺漏,“准确”即步骤正确,“简短”即没有多余的步骤。

  7. 根据缺陷或错误类型,选择图象捕捉的方式

  为了直观的观察缺陷或错误现象,通常需要附加缺陷或错误出现的界面,以位图的形式作为附件附着在记录的“附件”部分。为了节省空间,又能真实反映缺陷或错误本质,可以捕捉缺陷或错误产生时的全屏幕,活动窗口和局部区域。为了迅速定位、修正缺陷或错误位置,通常要求附加中英文对照图。

  8. 附加必要的特殊文档和个人建议和注解

  如果打开某个特殊的文档而产生的缺陷或错误,则必须附加该文档,从而可以迅速再现缺陷或错误。有时,为了使缺陷或错误修正者进一步明确缺陷或错误的表现,可以附加个人的修改建议或注解。

  9. 检查拼写和语法错误

  在提交每条缺陷或错误之前,检查拼写和语法,确保内容正确,正确的描述错误。

  10. 尽量使用业界惯用的表达术语和表达方法

  使用业界惯用的表达术语和表达方法,保证表达准确,体现专业化。

  11. 通用UI要统一、准确

  错误报告的UI要与测试的软件UI保持一致,便于查找定位。

  12. 尽量使用短语和短句,避免复杂句型句式

  软件错误管理数据库的目的是便于定位错误,因此,要求客观的描述操作步骤,不需要修饰性的词汇和复杂的句型,增强可读性。

  13. 每条错误报告只包括一个错误

  每条错误报告只包括一个错误,可以使错误修正者迅速定位一个错误,集中精力每次只修正一个错误。校验者每次只校验一个错误是否已经正确修正。

  以上概括了报告测试错误的规范要求,随着软件的测试要求不同,测试者经过长期测试,积累了相应的测试经验,将会逐渐养成良好的专业习惯,不断补充新的规范书写要求。此外,经常阅读、学习高级测试工程师的测试错误报告,结合自己以前的测试错误报告进行对比和思考,可以不断提高技巧。 
 
 
十、软件测试人员结构组成分析

      软件测试工程师是软件行业中一种即年轻又古老的职业,进入二十一世纪以来,随着中国加入WTO以后,从事这项职业的人也越来越多。一个公司在组建一个测试队伍的时候如何分配人员结构,从而使公司软件测试工作水平得到提高,是大家比较关注的问题。本人依照自己的经验提出自己的观点:


我们首先来看一下测试人员的纵向结构
1,测试经理
测试经理主要负责测试队伍的内部管理以及与其他外部人员,客户的交流,详细说来主要包括进度管理,风险管理,资金管理,人力资源管理,交流管理等等,测试经理需要具有项目经理的知识和技能。同时测试工作开始前项目经理需要书写《测试计划书》,测试结束需要书写《测试总结报告》
2,测试文档审核师
测试文档审核师主要负责前置测试,包括在需求期与设计期间产生的文档进行审核,比如《业务建模书》,《需求规格说明书》,《概要设计书》,《详细设计书》等等。审核需要进行书写审核报告。当文档确定后,需要整理文档报告,并且反映介绍给测试设计师。
3,测试设计师
测试设计师主要根据需求期与设计期间产生的文档设计各个测试阶段的测试用例。
(往往测试文档审核师,测试设计师可以有相同的一组人来完成)
4, 测试工程师
测试工程师按照测试用例,来完成测试工作。


但是测试人员应该有哪些人来组成呢?也就是测试人员的横向组成,让我们再来讨论讨论:
1, 需要具有一定开发经验的计算机专业人员
由于具有一定开发经验的计算机专业人员即懂得计算机的基本理论,又有一定的开发经验。所以对于软件中哪里容易出错,哪里不容易出错他们了如指掌;他们可以分析程序的性能,软件性能差是否是占有内存空间太多,或者是占有CPU时间太多引起的,还是其他原因,他们往往是专家。尤其是进行非功能测试的时候,他们可以更好的搭建系统测试平台。这种人员应该占测试队伍中一半以上。
2, 需要具有本软件业务经验的人员
测试队伍中需要有这样的人员的目的在于,这些人员由于对业务非常熟悉,软件质量的前提又是满足用户的需求。专业业务知识是计算机专业人员达不到的,所以这方面人才可以利用它们的业务知识和专业水平,参与系统需求期间的文当审核,可以发现软件中存在的业务性错误。比如专业用语不准确,业务流程不规范等等,这种人才对于专业性比较强的软件测试工作尤为重要,比如税务,法律,艺术,CAD,CAM
3, 只需要会操作计算机的人员
由于软件一旦卖出去之后,使用软件的人各种各样,各种各样的人带来各种各样的操作情况,请一大部分人员在软件测试工作后期进行测试工作是十分重要的,他们往往会发现专业测试人员测试不出的东西和一些希奇古怪的错误。这就是软件测试学中所谓的猴子测试法。
对于一个软件公司来说,并不是说所有的测试队伍都需要这三种人员,实际中可以一组人代替多个角色,但是要遵循以下原则:
1,对于业务不是很专业的软件,具有一定开发经验的计算机专业人员与具有本软件业务经验的人员可以合并;
2,只需要会操作计算机的人员,可以由公司行政人员来充当。

十一、软件测试的现实和理想
 
 
  “从我在微软工作的经历来看,软件测试绝对不是开发活动完成后的收尾工作,很多大型的开发项目,测试会占据项目周期一半以上的时间。以IE4.0为例,代码开发时间为6个月,而稳定程序花去了8个月的时间。”前微软亚洲研究院博士、软件测试专家陈宏刚谈道。从投入的资金和人力物力来看,测试、使产品稳定和修改花去的时间可能占到80%。
还处在婴儿期

  软件测试之所以发展相对缓慢,一个原因是做研究和做开发的人交流的机会相对少。只有做大型系统工程的人才会对测试提出较高的要求,重要性才能显现出来,而做研究和教学的人没有大型系统工程案例,所以造成了测试理论研究的发展缺乏充实的基础材料。真正做大型系统开发的工程师,又没有时间将第一手的测试经验变成系统的理论。

  “在美国,佛罗里达州和华盛顿州分别有一所大学开设软件测试课程,其他有正规课程的学校不是很多。软件测试正停留在没有学科系统、没有系统教育的阶段。虽然已经有学校开设了这门课程,但是使用的教学案例,多半是单机软件,还谈不上系统的理论。”陈宏刚博士介绍说。

  高素质的“杂牌军”

  由于企业对测试人才有着迫切的需要,因此,只好自己培养测试人才队伍。例如微软公司,对不同的产品制定测试规范,开设一些课程,通过讲座的形式对测试技术人员进行培训,但是也还未形成系统的理论。

  即使在微软,测试队伍是典型的“杂牌军”,没有科班,没有统一的专业,更多的是具有丰富的经验和不同行业背景的员工,例如具有语言学、数学、物理学、计算机、工程、管理等学科等背景的员工。但是,这不是说随便什么人都可以做测试工作,陈宏刚工作过的那个试验室,20个人中有7个博士。可见,虽然测试不是一个专门的学科,但是,这个部门对于一个成熟的软件企业又是至关重要的部门。

  认识需再提高

  IBM和微软公司属于领先的大公司,对测试的认识也经历了一个过程。开始的时候,也是开发人员兼职做测试,就像今天国内一些较小规模的软件企业。但是,后来的结果表明,花在软件修补上面的费用太高,以至于远远超出了所能够允许的范围。这个时候,增加测试队伍的规模,提高测试队伍的素质,提高测试队伍的待遇和受重视的程度是更加划算的。

  还有一个问题是,很多工程师不愿意做测试,认为是一种打下手的工作,没有前途,这也是国内比较大软件企业面临的问题。所以,企业从上到下普遍自觉和不自觉地只重视技术,不重视质量,后果是产品在市场上竞争力不高,产品售后维护和服务费用偏高。

  巨大反差

  微软的开发工程师与测试工程师的比例是1∶2,国内一般公司是6∶1。而且,致命的问题是没有哪个机构专门培养测试工程师。这个矛盾提示我们,在中国不能等到实际的需求和人力资源矛盾十分尖锐的时候,再谈培养问题;也不能等到产品质量成为产业阻碍的时候再来提高软件业的测试水平。测试工作不能靠手工劳动来完成,更多的情况是要使用工具软件和编写测试程序来完成,培养全面的测试专业人才是项任重道远的工作。

十二、软件测试的新模型   

      通常情况下,一个软件模型说明的内容主要包括,在测试过程中你应该考虑到哪些问题,如何对测试进行计划,测试要达到什么目标,什么时候开始,在测试中你要用到哪些信息资源。一个好的模型可以引导你对问题进行思考,而不好的模型则只能使你误入歧途。

      这里我要宣称的是,目前的大多数软件测试模型都是不好的模型。这是因为这些测试模型仅仅是软件开发模型的一些装饰和补充而已。

      人们一直在苦苦寻找软件开发的模型,在创建了新的模型后,就把测试作为一个阶段放在模型的后面部分。因此测试总被作为一种事后行为,测试总是被开发所驱动。总的来说,我们是在检测他们的完成品。但是,作为事后处理的测试,其驱动方式是不正确的。实际上它显而易见地和开发过程中各种行为之间有关,测试没有起到应有的平衡作用。这样的测试只是检测了开发人员做了什么,而并没有检测到他们是否按照规则做了什么,这样的做法割裂了本该紧密联系的行为,剩下的只有那些匆忙而草率的想法所带来的伤害。

      而这样做的结果就是效果很差的、效率很低的测试。效果很差的测试将导致很多bug没有被发现,而效率很低的测试所浪费的是成本。

      在本文中,我要做2件事,其一,我要否定一个不好的模型,即V模型。我希望通过论述来表明,“单元测试”和“集成测试”这2个词汇可以从我们的词汇表中取消了。其二,我将描述一个更好的模型。不过首先我认为,要真正拥有一个充分合理的模型还为时尚早。我仅仅是描述了一些新模型应该符合的重要的要求。这些要求将在本文末尾处列举。

      V模型有什么问题呢?

      在本文中我要把V模型作为不好的模型的典型来进行分析。我选择V模型作为分析的典型是因为V模型是最广为人知的测试模型。

      最典型的V模型版本一般会在其开始部分对软件开发过程进行描述,如下图所示:

 

  (图1--V模型的各级开发阶段)

      这是古老的瀑布模型。作为开发模型,它有很多问题,不过这里不作讨论。尽管它的各种状态是我们接着要讨论的大家最熟悉的V模型的基础。我的批评意见同时也针对其它的装饰在一些更好的开发模型之上的测试模型,例如螺旋模型[Boehm88]。

     在V模型中,测试过程被加在开发过程的后半部分,如下图所示:

 

  (图2--V模型示意图)

      单元测试所检测的是,代码的开发是否符合详细设计的要求。集成测试所检测的是,此前测试过的各组成部分是否能完好地结合到一起。系统测试所检测的是,已集成在一起的产品是否符合系统规格说明书的要求。而验收测试则检测产品是否符合最终用户的需求。

     对于测试设计,显而易见的是,V模型的用户往往会把执行测试与测试设计分开对待。在开发文档准备就绪后,就可以开始进行相关的测试设计。如下图所示,相应的测试设计覆盖在了相关的开发过程之上:

 

  (图3--将测试设计覆盖了开发过程后的V模型)

      V模型有着很吸引人的对称外形,并且把很多人都带入了歧途。本文将集中讨论它在单元测试和集成测试中引起的问题。

      为了说明的方便,这里专门制作了以下图片,图中包括一个单独的单元,以及一个单元组,我称之为子系统(subsystem)。

 

  (图4--一个假想的子系统)

      对于一个单元应该多大才最为合适的问题,已经有过很多的讨论,究竟一个单元仅仅是一个函数,一个类,还是相关的类的集合?这些讨论并不影响我在这里所要阐述的观点。我们权且认为一个单元就是一个具有最小程度的代码块,开发人员可以对进行独立地讨论。

     V模型认为人们首先应该对每一个单元进行测试。当子系统中所有的单元都已经测试完毕,它们将被集中到一起进行测试,以验证它们是否可以构成一个可运行的整体。

      那么,如何针对单元进行测试呢?我们会查看在详细设计中对接口的定义,或者查看源代码,或者同时对两者进行查看,找出符合某些测试设计中的有关准则的输入数据来进行输入,然后检查结果,看其是否正确。由于各单元一般来说不能独立地运行,所以我们不得不另外设计桩模块(Stub)和驱动模块(Driver),如下图所示。

 

  (图5:单元及其外部的驱动模块和桩模块)

      图中的箭头代表了测试的执行轨迹。这就是大多数人所说的“单元测试”。我认为这样的方法有时候是一种不好的方法。

      同样的输入也可以有同一子系统中的其它单元来提供,这样,其它的单元既扮演了桩模块,又扮演了驱动模块。如下图所示:

 

  (图6--子系统内部各单元间的测试执行轨迹)

      到底选择哪一种方法,这需要一种折衷和权衡。设计桩模块和驱动模块要付出多少代价?这些模块如何进行维护?子系统是否会由此而掩盖了一些故障?在整个子系统范围内进行排错的困难程度有多大?如果我们的测试直到集成测试时才真正开始,那么一些bug可能较晚才被发现。由此造成的代价同设计桩模块和驱动模块的代价如何比较?等等。

     V模型没有去考虑这些问题,当单元开发完成后就执行单元测试,而当自系统被集中在一起后就执行集成测试,仅此而已。令我奇怪和沮丧的是,人们从不去做一些权衡,他们已经受制于他们的模型。

      因此,一个有用的模型应该允许测试人员考虑节省并推迟测试的可能性。

      一个测试,如果要发现一个特定的单元中的bug,最好是在该单元保持独立的情况下执行,并且在其外部辅以特定的桩模块和驱动模块。而另一种方法则是让它作为子系统的一部分来进行测试,该测试的设计主要是为了发现集成的问题。由于一个子系统本身也需要桩模块和驱动模块来模拟该子系统和其它子系统的联系,因此,单元测试和集成测试可能被推迟到至少整个系统已经部分集成的时候。在这种情况下,测试者可能通过产品的外部接口同时进行单元测试、集成测试和系统测试,同样的,其主要目的还是为了减少总体生命周期的成本,对测试成本和延期进行测试及由此造成延期发现bug的代价成本进行权衡。据此而言,“单元测试”、“集成测试”和“系统测试”的区别已经大大削弱了。其结果可参考下图:

 

  (图7--新的方法:在部分阶段延迟进行单元测试和集成测试)

      在上图右边的方块中,最好要改成为“执行某些适当的测试并得到相应的结果”。

     图中的左边会怎样?考虑一下系统测试设计,它的主要根据和信息来源是是规格说明。假设你知道有2个单元处在一个特定的子系统中,它们在运行时相互联系,并且要执行规格说明中的一个特定的声明。为什么不在该子系统被集成时立即对此规格说明中的声明进行测试,就象是在设计完成后立即开始测试的设计一样呢?如果该声明的执行和子系统外的子系统没有任何关系,为什么还要等到整个系统完成以后再测试呢?难道越早发现bug成本越低不对吗?

     在上一张图片中,我们用了向上指的箭头(更有效,但在时间上有延迟)。这里还可以把箭头往下指(在时间上提前):

 

  (图8--新的方法:在不同阶段上提前进行测试设计)

     在这种情况下,左边的方块中最好被标记为:“在当前信息条件和情况下可以做的任何测试设计”。这样,当测试设计得自于系统中某一个组件的描述时,模型必须允许这样的测试在组件被装配之前被执行。我必须承认我的图片非常难看,这些箭头指得到处都是,对此我有2点说明:

  1. 我们所讨论的事情不是创造美,而是想要发现尽可能多的严重错误,同时尽可能地降低成本。

  2. 难看的部分原因也是因为必须按照某些次序来执行的结果,亦即开发人员先提供系统描述文档,然后测试和这些文档进行关联。这些文档就象是坚实的老橡树,而测试设计则象是细细的枝条缠绕在树上。如果我们采用不同的原理来进行组织,图片可能就会变得好看些。但复杂性仍不可避免,因为我们要讨论的问题本身就很复杂。

      V模型失败的原因是它把系统开发过程划分为具有固定边界的不同阶段,这使得人们很难跨过这些边界来采集测试所需要的信息。有些测试应该执行得更早些,有些测试则需要延后进行。而且,它也阻碍了你从系统描述的不同阶段中取得信息进行综合。例如,某些组织有时执行这样的做法,即对完成的工作进行签署。这样的规定也扩展到系统测试的设计。签署表示已经过评估,该测试设计工作已经完成,除非对应的设计文档改变,否则就不会被修订。如果同这些测试相关的信息后来被重新挖掘和认识,例如,架构设计表明有些测试是多余的,或者,详细设计表明有一个内部的边界可以和已存在的系统测试组合在一起进行测试的话,那么实际上还需要继续调整原来的系统测试设计。

      因此,模型必须允许利用不同来源的综合信息进行个别的测试设计。另外,模型还应该允许在新的信息来源出现后重新进行测试的设计。

     一个不同的模型

     让我们来看本文的第二项内容,一个不同的模型。

     很多时候人们把代码移交给其他人,并且说:“希望你能接受和喜欢它。”这不仅发生在将整个项目放在一张光盘中交给客户的时候,也发生在项目内部。

     例如,一个小组对另一个小组说:“我们已经完成了为COMM库加入了对XML的支持。源代码现在已经放在master库中,可执行库则已经加入到集成与创建的环境中。XARG小组的工作已经没有什么阻碍了,随时去取吧。”

      某个程序员检查了bug的修改并且发出邮件:“我已经修改了Bug列表中的那个Bug,很抱歉!”至此,早先受该问题影响的其它代码就可以继续处理了。

     在这些情况下,人们要把代码移交给其它人,其中有可能会存在一些影响。测试人员需要干预这个过程。在移交之前,测试人员应执行这些代码,发现其中的bug(影响),并且提出问题:“你确实要提交这些吗?”由此,移交的内容可能会被延期,直到bug被修复好。

      尽管你还要做其它的各种测试,这项测试仍然是很基本的测试工作。如果你没有做这样的测试,就不能算是合格的测试人员。

      我们的测试模型必须包含这一重要的现实需要:针对代码移交的测试。由此,测试模型应提示进行针对每一次代码移交的测试。

      就让我以支持XML的COMM库作为例子。这里存在着一个小组把代码移交给XARG小组以进行项目的余下部分。那么谁会遭受影响?

      要将这些支持XML的代码直接进行使用的XARG小组可能会立即受到影响;

      这可能会在稍后影响到市场人员,他们要在一个行业展示会议上为“合作伙伴发行”版本提供产品演示和宣传,而XML支持是影响他们销售的重要部分;

      还有,它可能损害采纳我们的产品的合作伙伴。

      现在我们可以提出一些有趣的关于测试计划的问题了。最简单的可以做的事情是,在移交的时候立即执行XML支持的完整测试。(“完整”的含义是,为此设计尽可能多的测试)但也许一些XML特性并不是XARG小组所需要的,因此可以把它们放在合作伙伴版本封版(下图中的“Partner Release”)的测试中去。这意味着可以把一些XML相关测试放到稍后的移交过程中去。或者基于其它理由,例如在近阶段有其它的测试任务要执行。而XARG小组则要因XML中的bug修复而延迟一小段时间。

      我们的测试计划所表示的进度可以通过在开发的时间线上进行注解的方式来表现,如下图所示:

 

  (图9:添加在开发计划之上的测试计划)

      我们应严格地围绕在XML支持的功能交接的时段里进行测试。测试设计和测试支持工作要早于测试执行。而另外的XML测试则要延迟到基于整个项目范围的“代码完成”(图中的“Code Complete”里程碑),或者要等到全部的子系统被集中在一起,而且整个产品为了行业会议而在经过稳定化处理后创建了版本(图中的“Partner Release”里程碑)。

      显然,有两项内容没有包含在代码完成里程碑中:

      还有大量其它的测试工作(包括设计、工具选用)。这些工作可能因为COMM以外的子系统的交接而延期。

      而且,还有用于完成里程碑中所规定的某些风险的测试,例如,可能还有一组用于运行市场人员的演示Demo脚本的测试,包括她可能在无意中引起的偏离。其目的是要避免这样的情况,即当她站在1000人的观众面前时,她还仅仅是第一次以某种特定的顺序来输入数据。

      一些首次交接时进行的XML测试需要在代码完成里程碑上再次执行。

      我的观点是,测试计划是由很多困难的决定所组成,这些决定包括人员组织安排、机器资源配置、测试设计的时间定位、测试支持代码的数量、哪些测试要做自动化,等等。这些决定应根据单独的交接中的内容信息来作出。如果仅有一次交接,那么你可以更顺利一些。测试计划还应继续考虑以下问题:

  1. 风险分析,谁会因此受到损害,以什么方式?

  2. 选定一种测试途径来定位特定的风险。

  3. 对测试设计和执行的周期和成本进行估计。

  4. 在项目进度上的特定位置,将计划纳入执行的行动:

  A. 开始对测试进行设计…

  B. … 同时设计和创建一些支持测试的代码…

  C. … 在全部测试完成以前就执行部分的测试,因为可能存在不只一次的交接,在每一次交接的测试规划中,可能存在一些潜在的复杂的相互影响。工作安排不得不进行一些调整以达到相互间的平衡。测试支持代码和工具需要在各项任务中得到共享。你还必须考虑到在什么程度上让那些为早先的交接所设计的测试在以后重新执行,等等。

      这看起来很复杂。看上去似乎有太多的内容需要跟踪,而且太多的内容可能被忽略。也许你认为我是在要求你要对每一次交接来执行IEEE 829 [IEEE98]中关于测试计划的要求,然后把它们合并为一份贯穿整个项目的针对交接进行测试的测试计划。

      是,也不是。思考问题总是要占用时间的。太多的计划可能会减少结果的产出。在有些时候,你需要做的是停止计划并开始行动。例如,你无法思考并描述每一个bug修复,尽管bug修复也是一种交接。

      但是bug修复是实际工作中现实存在的问题。总体项目计划中应该包含bug修复。需要强调的是,你应该有一个默认的bug修改处理的标准过程,该过程应包括运行于每一个提交的bug修复的验证过程。你还需要努力地去思考问题。很多时候,各项验证是被放在一起同时进行并完成的。

      比较现实地来说,一个模型应该允许一些机械式行为,例如,“不管是哪一个X类型的交接,都要执行下列操作”。同时我们鼓励对特定的交接执行刚刚够的检查,对于风险越小的交接,就越可以采用机械式的测试行为。

      一个明确考虑到基本的测试现实的模型肯定会比忽略这些现实或者把你的工作复杂性完全抽象化的模型做得更好。文档则是另一个例子。

      我还没有提到需求及规格说明书,或者设计文档。某个交接中产生的一系列变化会引起很多争议。这些文档所扮演的角色是什么呢?它们常常是这么被使用的:

 

  (图10:测试中对开发文档的利用)

      文档可以指导你在一个交接变化时如何作出反应。如果你有一份很好的需求文档,它可能是产品所解决的问题的描述,尽管也许不是很直接。它可以帮助你对风险进行分析。一份好的规格说明应对系统的行为进行描述。这将帮助你把测试方法转化为具体的测试。一份好的架构设计则可以帮助你理解变化可能引起的几种不同的情况:系统的其它部分会受到怎样的影响?什么测试需要再次进行?

      我并不是经常能看到好的文档。需求文档常常象是市场销售用的系统特性列表。规格说明书有时就象是在代码完成后提交的用户手册文件。而设计文档经常不存在。

      好了,通过针对交接所引起的变化的集中讨论,我已把测试过程和软件开发过程相对地分离开了。如果文档中关于“XML支持功能加入到COMM”的描述很薄弱的话,我会尽自己所知来进行尽可能好的测试设计。然后,假如在项目后期,XML相关的用户文档出来了,我就可以对后来再次提交的交接增加相关的测试。假如市场需求改变了,她们经常会这么做,我还会在此后增加或者去除一些测试。所有这一切看起来是这样的:

 

  (图11--在文档不完整的条件下进行测试,并在后期补充测试)

      这样,虽然项目文档总是不到位,而且经常延迟提交,测试的效果也因此常常被降低,我们还是要避免测试受到项目文档的制约。

      头脑灵活的测试人员并不过于相信文档。毕竟,总是人在犯错误。那么,难道不是人在写这些文档吗?

      由于“正式的”文档是很薄弱的,测试模型必须明确地鼓励在测试过程中使用项目文档之外的各种不同信息来源。

      测试人员必须和程序员、用户、市场人员、技术作者以及任何的可能为实现更好测试提供线索的人进行交流。测试人员还应该努力把自己沉浸在某些技术所构建的氛围中。例如,我希望测试人员在做XML测试工作时常去访问W3组织的XML地址(http://www.w3.org/XML/)以及其它XML站点、邮件列表,甚至包括比较特别的 如Dave Winer的DaveNet/脚本新闻(http://www.scrīpting.com/)。这些资源并不是所谓的“辅助通道”,而是可以被列入计划和进度日程的资源。

      另外,所执行的测试本身也是一种有用的信息的资源。好的测试人员会仔细阅读bug报告,因为这些报告讲授了系统所存在的薄弱之处。特别地,这些报告还暗示了一些正式的架构设计所没有提供的架构上的策略。执行测试的行为应该产生一些新的测试想法。如果模型没有考虑到这些,那么它就是一个落后的模型。

      因此,测试模型应该包含反馈的循环,让测试设计可以考虑到,在运行测试时还可以继续发现到更多的测试内容。

      在我们的工作中,真正的复杂性来自于所有计划的执行都处于一个不确定的、容易忽略的环境里。代码并不是唯一在不断变化的东西。而计划日程也在改变。新的功能扩充会带来新的里程碑。某些功能会从当前版本中去除。在开发过程中,所有人--市场人员、开发人员和测试人员,都会逐渐对诸如“产品究竟提供什么”这样的问题有越来越清晰的了解。在这些情形下,我们怎么能说测试计划的第一个版本会是完全正确的呢?

      因此,模型应该要求测试计划人制定明确的规定,对已交接的交接内容,新的交接,以及交接内容的变更进行负责。

      总结

      V模型有以下致命的缺陷,其它模型实际上也与此相似:

  1. 忽略了这样的事实情况,即软件开发是由一系列的交接所组成,每一次交接内容都改变了前一次交接的行为。

  2. 依赖于开发文档的存在,及文档的精确性、完整性,并且没有对时间进行限制。

  3. 认定一种测试的设计是依据某一个单独的文档,而不包括根据其前后阶段的文档的修改而作相应修改。

  4. 认定这些依赖于某个单独文档的测试一定要在一起执行。

      我大致描述了一个替代模型,但还不够精细。它考虑到了代码的交接和里程碑。对测试成本控制作了以下明确描述:

      测试设计的目标是定义好可能发现bug的测试输入,而测试执行的目标是以各种方式加入这些数据,并检验结果,由此来降低整个生命周期的成本。

      我们的模型假设软件产品总是不完美的,开发过程中有很多变更,而且对产品的测试也是一个不断学习的过程。

      过去,我很少考虑到模型。表面上我一直还在用V模型。虽然我按此制订计划,但我总是还要花费很多额外的精力和时间来考虑模型中没有提到的方面。换言之,模型造成了一些阻碍,因此我有必要对此进行研究。

      对一个新的模型来说,对模型所提出的要求必须非常明确,这就象业务需求对产品开发非常重要一样。我希望自己对本文中所提倡的模型的要求的描述能够和V模型中的描述一样精确,并具有同样的指导意义。

      理想的测试模型应该包括下列要求:

  1. 使测试对项目中的每一次代码交接有所反应。

  2. 要求测试计划人制定明确的规定,对已交接的交接内容,新的交接,以及交接内容的变更进行负责。

  3. 在测试设计中,除了使用项目文档外,还应明确鼓励使用其它各种信息,这些信息有不同来源。

  4. 事实上项目文档总是不到位,而且经常延迟提交,测试的效果也因此常常被降低。但我们还是要尽量避免测试受到项目文档的制约。

  5. 允许根据多种来源提供的综合信息来设计一些独立的测试。

  6. 让测试被重新设计,以新的信息形式进行表现。

  7. 包含反馈的循环,让测试设计可以考虑到,在运行测试时还可以继续发现到更多的测试内容。

  8. 让测试人员认识到,避免测试的延迟可以节省成本。

  9. 在组件被组装到程序中去之前对组件的执行进行测试。

posted @ 2008-07-15 18:41 Niino 阅读(410) | 评论 (0)编辑 收藏

C++编程风格指南

posted @ 2008-07-12 11:08 Niino 阅读(106) | 评论 (0)编辑 收藏

有一个农夫决定买一匹骡子,他认为这个骡子至少得能扛动3袋大米,他才会决定买这匹骡子(用户提出的性能需求)。

他来到农贸集市上,试了好几头骡子,都不合适,最后终于有一头骡子能够比较轻松的扛动这3袋大米,而且还潇洒的走了几步(性能测试通过)。

农夫想看看这头骡子到底能拉多少大米,于是一袋袋的往骡子身上加,加到第7袋的时候,骡子双腿打颤,卖骡子的心疼起来,立刻制止,农夫满意的买下了这头骡子。(容量测试通过)

农夫高高兴兴地牵着这头骡子回家,而且给它扛了4袋大米(系统超负荷运行),因为他跑了太远才买到了这匹不可多得的骡子,他想看看它到底能有多强,所以农夫决定,让这匹骡子就扛着这四袋大米走回家试试看.(在超负荷情况下检验系统能正常运行多久,进入压力测试)

这匹骡子真的很厉害,刚开始的时候还一颠一跑的,可是家离集市有5公里,骡子越驮越费劲。快到家的时候,已经是走两步歇一步了。终于到家了。(压力测试通过)

农夫非常自豪地叫出自己的老婆,说:“老婆子,快来看看,看我买到了一头多么厉害的骡子啊!”,老婆出来后,农夫把他和骡子在一路上的经历都告诉了老太婆,谁知这个老太婆却说:“你真蠢,这么大老远的路,也不让骡子驮着你,竟然和这头傻骡子一样走回来!”,农夫听了,觉得非常后悔,说:“那好吧,既然在路上它没有驮我,那就让它现在补上,也算是对我的补偿。”,骡子还没有反应过来,就看那老农夫一个箭步,跳到了骡子背上(这相当于容量测试的极限点),可怜的骡子,无论如何也不会想到,这狠心的农夫竟然在它走了这么久之后,不但没有帮它卸掉身上的重担,更没有给它喝口水,竟然变本加厉的跳到了它那本已弯曲的背上。可怜的骡子啊,就这么一命呜乎了!就看见那个骡子、农夫和4袋麦子一起轰然倒地。(相当于已经到了系统的最大拐点,造成了系统瘫痪,无法使用)。

posted @ 2008-07-10 15:00 Niino 阅读(128) | 评论 (0)编辑 收藏

 

当一个测试人员证实了程序里充满bug的时候,他是一个好的还是一个糟糕的测试人员呢?在某些开发人员看来,这是一份糟糕的工作。看上去荒谬可笑,因为项目经理等人会因产品的延期交付而责备测试人员,而且开发人员会抱怨(通常是以玩笑的形式):“测试人员对于程序过于较真。”因此很明显的,还有比计bug数更好的测试方法。这里是一些测试人员如何应对开发人员的小窍门。

当我作为一个测试人员开始我的工作时,我意识到在开发人员和测试人员之间存在一种持久的对抗关系,而且我毫不费力的相信了:这太常见了!我接受开发人员不欢迎的态度,因此我认为所有的测试人员在他们工作的不同时刻都会有相同的经历。从冷漠的不屑一顾到坦白的敌对相视(有时掩饰以同情的微笑),一个测试人员不得不忍受开发人员很多。这样很难保持测试人员积极的态度。但我们测试人员积极的态度取决于我们保持的优先权和保证项目质量的责任。我从Cem Kaner的《计算机软件测试》一书中摘得一句优美的话:“最好的测试人员不是发现了最多个bug或者使最多的开发人员感到不安的那个人,而是使开发人员fix最多bug的那个测试人员。”那么,我们从中能得到什么样的启发呢?

态度诚恳,有耐心。

作为一个测试人员,你也许发现说服一个开发人员修改你发现的缺陷比你发现缺陷本身更难。通常情况是,测试人员发现一个bug,开发人员会准备好十个理由来反驳。对于开发人员而言。有时很难接受自己的代码有缺陷这一事实——即便是另外一些人已经查明确实如此。开发人员需要测试团队的支持,他们能说服开发人员发现一个新的bug,对于尽可能使产品达到最好这一目的,是想望的、具有建设性的并且是非常重要的。采用一种人性化的方式,将更有利于测试人员了解开发人员。相信我,没有这样的一个人会和你坐在一起嘲笑他自己引出的bug。诚恳地态度常使开发人员说:“是吗?多亏你的bug报告,我得到一个非常重要的进步!”

要善用交际手段。

试着机智圆滑的展示你发现的bug并不带任何抱怨的解释它:“我肯定这是一个很小的bug,你会马上解决掉它。这是迄今为止非常完美的程序。”开发人员会非常欢迎解决它。

要善于采取心理战术,

时不时地赞美开发人员的工作。大多数开发人员不喜欢我们的bug报告的原因很简单:他们认为我们破坏了他们的辛勤劳动的成果。一些测试人员在只有发现问题的时候才与开发人员交流。对于开发人员而言,软件就像自己的孩子,而你测试人员只是一个外来的干预者。我告诉我的开发人员因为他们我才能留在公司,并且因为我,他们工作上的失误才能得以补救。这是在开发人员和测试人员之间的一种具有象征意义并且非常有益的关系。

不要使开发人员不安。

没有人喜欢别人指出自己的错误,这是人的本性。试着解释fix某个bug的具体办法,譬如需要一个大的图片,远比自顾自的提一大堆bug报告好的多。一大堆的缺陷报告不仅不能使开发人员着急,还会使你的辛苦工作在他们看来毫无用处。就像测试人员不能对程序测试完全一样,开发人员也不可能设计出没有错误的程序。他们比需要其他事情更强烈的需要测试人员的理解。我们期望出现错误,因为他们是整个过程的一部分。

有得必有失

我知道测试人员尽可能的将bug报告提的很严格。他们甚至不去听取开发人员关于这个bug不能fix或者是为了实现某个特性的解释。试着让自己放松下来,坐下来和开发人员一道分析这个bug的优先级和严重程度,如果这个开发人员对于不乐意修改这个bug有合理的和明智的解释的话,试着去理解他。只是要确保哪里是保证产品质量的底线。

警惕心理

外交手段和弹性应对并不能替换必需的警惕心理。开发人员经常找理由解释他们拒绝fix一个bug时,说因为他们没有意识到(或者你没有告诉他们)这个问题有多严重。设计你的bug报告和测试文档,使其能清楚地显示出问题的风险和严重程度。甚至更好的办法是召开一次会议,向开发人员解释这个bug。一个聪明的测试人员是一个在聆听与表达之间取得一个平衡的人。如果一个开发人员不能说服你不fix一个bug时,说服他fix这个bug就是你义不容辞的责任了。


转自 http://www.51testing.com/?59943/action_viewspace_itemid_86925.html
posted @ 2008-07-10 11:12 Niino 阅读(211) | 评论 (0)编辑 收藏
仅列出标题
共3页: 1 2 3 
<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

常用链接

留言簿(2)

随笔档案

文章档案

搜索

  •  

最新评论

阅读排行榜

评论排行榜