原创作品,出自 “晓风残月xj” 博客,欢迎转载,转载时请务必注明出处(http://blog.csdn.net/xiaofengcanyuexj)。
由于各种原因,可能存在诸多不足,欢迎斧正!
摘要
软件测试(software testing)是在规定的条件下对程序进行操作,以发现程序错误,衡量软件质量,并对其是否能满足设计要求进行评估,其目的在于鉴定软件的正确性、完整性、安全性和质量。
关键字
软件测试
引言
伴随着计算机的快速发展,软件产品被运用到人类生活的各个领域,软件的规模与复杂度也不断变大,使得软件测试显得越来越重要。随着软件行业的发展,软件企业要在国内站稳脚跟,跨出国门,产品质量成为制约国内企业生存的核心因素,也关系到其它行业的企业国际化道路。国内企业在国际化的过程中,已经认识到软件质量在推动提升企业整体竞争力中的作用,做好软件测试工作才是解决软件质量问题的根本。在此背景下,软件测试技术应运而生。
正文
1.软件测试定义
IEEE定义的软件测试:从通常是无限大的执行域中恰当的选取一组有限测试用例,对照程序已定义的预期行为,动态的检测程序的行为。
对于这个定义,我认为可以抓住这样几个关键词理解:“动态”,“恰当的选取”,“有限测试用例”。“动态”即我们的测试值是随机选取的,不能人为的干预;“恰当的选取”是关键所在,好的选取能起到事半功倍的效果;“有限测试用例”即测试的资源消耗是有限的,不能占用过多的时间与空间资源。测试永远都意味着有限资源和计划进度与本质上是无限测试需求的折中。折中策略的好坏直接关系到软件测试质量的高低。
我理解的软件测试的定义:从程序的内在逻辑设计与外在功能特性两个方面检测程序是否能完成预期的任务的思想、方法、技术。且这种思想、方法、技术应该运行效率高,资源占用少。至少可以从两个方向考虑:正向思维与逆向思维。前者的出发点是验证程序正确,后者的出发点是验证程序错误。软件测试应该区别于程序调试(debug)。程序调试是将编制的程序投入实际运行前,用手工或编译程序等方法进行测试,修正语法错误和逻辑错误的过程。程序测试往往是局部性的、小规模的。而软件则是全局的、宏观的。
2.软件测试的基本思想及方法
软件测试的基本思想和方法有很多种,下面是我理解的一些分类方法。
按照思维方法大致可以分成两类:第一类方法、第二类方法。
第一类方法:试图验证软件是“工作的”,所谓“工作的”就是指软件的功能是按照预先的设计执行的,以正向思维,针对软件系统的所有功能点,逐个验证其正确性。
第二类方法:试图验证软件是“不工作的”或者说是有错误的。测试是为了证明程序有错,首先认定软件是有错误的,然后用逆向思维去尽可能多的发现错误。
第一类方法以需求和设计为本,有利于界定测试工作的范畴;第二类方法则更强调测试人员的主观能动性,用逆向思维,不断审查开发人员的逻辑误区、代码缺陷。
按照测试的内部特性还是外部特性可以分为白盒测试与黑河测试。
黑盒测试也称功能测试,它是通过测试来检测每个功能是否都能正常使用。在测试中,把程序看作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,在程序接口进行测试,它只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数据而产生正确的输出信息。黑盒测试着眼于程序外部结构,不考虑内部逻辑结构,主要针对软件界面和软件功能进行测试。
白盒测试也称结构测试或逻辑驱动测试,它是按照程序内部的结构测试程序,通过测试来检测产品内部动作是否按照设计规格说明书的规定正常进行,检验程序中的每条通路是否都能按预定要求正确工作。 这一方法是把测试对象看作一个打开的盒子,测试人员依据程序内部逻辑结构相关信息,设计或选择测试用例,对程序所有逻辑路径进行测试,通过在不同点检查程序的状态,确定实际的状态是否与预期的状态一致。
3.我的软件测试技术实践环节:
1.字符识别
在软件测试中,常常需要识别一些字符序列,依此断定哪些是合法的,那些是不合法的。例如在C\C++的库函数中有很多是不安全的,即没有相应的内存保护环节,如(strcpy());又如某些程序片段可能容易感染病毒或造成其他一些问题…这内问题的核心就是需要在程序中判断是否有指定的不合法或不安全的状态出现。对于这个问题,我们可以提出一个简单的模型:有限单词集W[i],有限长度句子S,判断单词是否出现在句子中。
说明:
有限单词集:表示不合法或不安全的字符序列;
有限长度句子:表示待判断的源程序。
假定采用第一类方法,假定程序是正确的就不好判断了;此处可以采用第二类方法,逆向思维,假设源程序S包含有有限单词集W[i]的某些单词,剩下任务的就是验证假设。此处我们是从外部功能特性还是内部结构特性着手呢?根据我的编程经验,在用C\C++时调用库函数一般不会出现问题,可能在某些极端苛刻的条件下会出现一些问题,但难以捕捉到,不适合从外部功能特性入手,所以应用白盒测试。
我们现在要做的工作就是判断是否存在word∈W[i]且word∈S,有则输出满足条件的word.
这个模型就转化成了我们熟悉的字符匹配问题了。关于字符匹配的算法有很多,在此我选取了其中最有代表性的几种算法。
(1)、单个的处理每个单词word∈W[i],结合BF算法或KMP算法判断其是否在S中。
(2)、把有限单词集W[i]看做一个整体,结合有限自动机DFA技术综合考虑判断其是否在S中。
具体代码见“字符匹配.cpp”,允许单词重叠,即如“aaa”在“aaaa”中记作出现2次。
2.内存问题
常见的软件错误有很多种,但很多都可以归结为内存问题。常见的内存问题有内存访问越界,内存资源泄露等。内存问题往往是软件测试中最难处理的问题之一,有高度的隐蔽性且发生概率不小;而内存问题带来的后果极其严重,轻则程序崩溃,重则带来病毒或木马程序。
内存测试的主要任务是测试程序中的内存访问越界,内存资源泄露等。内存访问越界有数组下标越界、栈溢出等;内存资源泄露动态分配的内存资源没有释放、打开文件没有关闭、Windows窗口句柄没有关闭等。
此外,在进行内存测试时还需考虑系统资源在某些极端情况下的软件行为。如系统中的资源已经被别的程序占用很多,或程序长时间运行导致的问题等等。
内存访问越界,内存资源泄露可以通过开发人员的良好编程风格、严谨踏实的职业精神在某种程度上防范于未然。而软件在某些系统资源极端情况下的行为则往往难以预测,一来因为计算机技术的飞速发展使得内存容量很大,可以满足很多软件的需求;二来是投入应用的软件对内存的要求通常不会太高,二者综合起来促成了人们对系统资源极端情况下的软件测试工作的忽视。尽管如此,仍有一些大型软件需占用大量的系统资源,因此有必要测试软件在极端情况下的行为。
结合操作系统的相关知识,我设计了一个虚拟内存碎片模拟器。设计思路如下:首先把内存分成一块一块的碎片,全部标记为占用;然后随机释放一定数目的内存碎片,这样可用的内存资源就是被释放的那部分,于是就构造了一个有很多内存碎片的系统环境。这样的系统环境是不能满足一些对内存要求较高的程序需求的。我们可以将其嵌入我们的待测试的软件中,去观察极端环境下软件运行情况。
下面我自己写了个虚拟内存碎片模拟器,由于要用到“windows.h”头文件里的函数,所以部分参照了电子工业出版社《软件测试》第204页的代码,在次申明并表示感谢。具体代码见“虚拟内存碎片模拟器.cpp”。
//虚拟内存碎片模拟器.cpp
#include<windows.h>
#include<time.h>
#include<stdio.h>
static void **ExternMemory=NULL;
static UINT *ExternpuMemSize=NULL;
static UINT ExternMemBlock=0;
/********************************************************
*参数1:uFreeSize表示待释放内存碎片的总容量
*参数2:uMaxSize表示每块内存碎片的最大容量
*功能:将内存切割成一块一块的小碎片
********************************************************/
void MemSimulator(UINT uFreeSize,UINT uMaxSize)
{
void **Memory;
UINT *puMemSize;
UINT uMemBlock;
MEMORYSTATUS ms;
GlobalMemoryStatus(&ms);
UINT uAvailMemSize=ms.dwAvailPhys;
/// UINT uAvailMemSize=ms.dwAvailPageFile;
ExternMemBlock=(uAvailMemSize/uMaxSize)*2+1;
uMemBlock=ExternMemBlock;
Memory=(void **)new void*[uMemBlock];
puMemSize=new UINT[uMemBlock];
uAvailMemSize-=uMemBlock*sizeof(void*);
uAvailMemSize-=uMemBlock*sizeof(UINT);
ExternMemory=Memory;
ExternpuMemSize=puMemSize;
//将内存分成一个个被占用的小块
srand(time(NULL));
UINT i=0;
UINT uTotalMemlen=0;
for(i=0;i<uMemBlock;i++)
{
if(uTotalMemlen<uAvailMemSize)
{
UINT uMemLen=rand()%uMaxSize;
if(uMemLen<64)
{
uMemLen=64;
}
Memory[i]=new char[uMemLen];
puMemSize[i]=uMemLen;
uTotalMemlen+=uMemLen;
}
else
{
Memory[i]=NULL;
puMemSize[i]=0;
}
}
//随机释放掉总容量为uFreeSize的内存空间
UINT uTotalFreeSize=0;
srand(time(NULL));
for(i=0;i<uMemBlock;i++)
{
UINT uPos=rand()%uMemBlock;
if(Memory[uPos]!=NULL)
{
delete []Memory[uPos];
Memory[uPos]=NULL;
uTotalFreeSize+=puMemSize[uPos];
}
if(uTotalMemlen>=uFreeSize)
break;
}
}
/********************************************************
*无参数
*功能:释放全部的内存空间
********************************************************/
void CloseMemSimulator()
{
if(ExternMemory!=NULL)
{
UINT i;
for(i=0;i<ExternMemBlock;i++)
{
if(ExternMemory[i]!=NULL)
{
delete []ExternMemory[i];
}
}
delete []ExternMemory;
delete []ExternpuMemSize;
}
}
void main()
{
printf("Memory Simulator Start.\n");
printf("This will take several minutes,please wait...\n");
MemSimulator(1024*1024,1024);
printf("Memory Simulator running,press enter key to exit!\n");
getchar();
printf("Begin to free the memory,please wait...\n");
CloseMemSimulator();
printf("Memory Simulator Finish.\n");;
return ;
}
源程序清单
//BF.h
/********************************************************
*参数1:p指向模式串
*功能:BF算法统计模式串str2在主串str1出现的次数
********************************************************/
int BF(char *str2,char *str1)
{
int i,j,len1,len2,ret=0;
len1=strlen(str1);
len2=strlen(str2);
i=0;j=0;
while(i<len1)
{
if(str1[i]==str2[j])
{
i++;j++;
}
else
{
i=i-j+1;j=0;
}
if(j==len2)
{
i=i-j+1;j=0;
ret++;
}
}
return ret;
}
</pre><pre code_snippet_id="133475" snippet_file_name="blog_20131229_5_1684942" name="code" class="cpp">//KMP.h
int next[50+5];
/********************************************************
*参数1:p指向模式串
*功能:KMP算法中计算next[]数组
********************************************************/
void getNext(char *p)
{
int j,k,len=strlen(p);
j=0; k=-1;
memset(next,0,sizeof(next));
next[0]=-1;
while(j<len)
{
if(k==-1||p[j]==p[k])
{
next[++j]=++k;
}
else k=next[k];
}
}
/********************************************************
*参数1:W指向模式串
*参数2:T指向模式串
*功能:统计模式串W在主串T中出现的次数
********************************************************/
int KMP_count(char *W,char *T)
{
int i,wlen=strlen(W),tlen=strlen(T),j=0,ans=0;
getNext(W);
for(i=0;i<tlen;i++)
{
while(j>0&&T[i]!=W[j])
j=next[j];
if(W[j]==T[i])j++;
if(j==wlen)
{
ans++;
j=next[j];
}
}
return ans;
}
</pre><pre code_snippet_id="133475" snippet_file_name="blog_20131229_5_1684942" name="code" class="cpp">//DFA.h
#include<iostream>
const int kind=52;
struct DFANode
{
DFANode *fail; //失败指针
DFANode *next[kind]; //Tire每个节点的26个子节点(最多26个字母)
int count; //是否为该单词的最后一个节点
DFANode()
{ //构造函数初始化
fail=NULL;
count=0;
memset(next,NULL,sizeof(next));
}
}*q[50*1000+10]; //队列,方便用于bfs构造失败指针,大小应依据Tries图节点个数而定
int head,tail; //队列的头尾指针
//
/********************************************************
*参数1:str[]便是有限单词集
*参数2:root表示相应DFANode的根节点
*功能:建立一颗以root为根节点的不带前缀指针的字典树
********************************************************/
void insert(char *str,DFANode *root,int ind)
{
DFANode *p=root;
int i=0,index;
while(str[i])
{
if(str[i]>='A'&&str[i]<='Z')
index=str[i]-'A';
else index=str[i]-'a'+26;
if(p->next[index]==NULL)
p->next[index]=new DFANode();
p=p->next[index];
i++;
}
p->count=ind;
}
/********************************************************
*参数1:root表示相应DFANode的根节点
*功能:在建好的字典树上添加前缀指针,形成Tries图,DFA
********************************************************/
void build_ac_automation(DFANode *root)
{
int i;
root->fail=NULL;
q[head++]=root;
while(head!=tail)
{
DFANode *temp=q[tail++];
DFANode *p=NULL;
for(i=0;i<52;i++)
{
if(temp->next[i]!=NULL)
{
if(temp==root)
temp->next[i]->fail=root;
else
{
p=temp->fail;
while(p!=NULL)
{
if(p->next[i]!=NULL)
{
temp->next[i]->fail=p->next[i];
break;
}
p=p->fail;
}
if(p==NULL)
temp->next[i]->fail=root;
}
q[head++]=temp->next[i];
}
}
}
}
/********************************************************
*参数1:root表示相应DFANode的根节点
*功能:查询有多少种模式串出现在有限长度句子中
********************************************************/
void query(char *str,DFANode *root,int num[])
{
int i=0,index,len=strlen(str);
DFANode *p=root;
while(str[i])
{
if((str[i]>='A'&&str[i]<='Z')||(str[i]>='a'&&str[i]<='z'))
{
if(str[i]>='A'&&str[i]<='Z')
index=str[i]-'A';
else index=str[i]-'a'+26;
while(p->next[index]==NULL && p!=root)
p=p->fail;
p=p->next[index];
p=(p==NULL)?root:p;
DFANode *temp=p;
while(temp!=root&&temp->count)
{
num[temp->count]++;
temp=temp->fail;
}
}
else
p=root;
i++;
}
}
//字符匹配.cpp
#include<iostream>
#include<windows.h>
#include"BF.h"
#include"KMP.h"
#include"DFA.h"
using namespace std;
char keyword[1000+5][50+5]; //输入的单词
char str[2000010]; //模式串
int num[1000+10];//统计的次数
void BF_Solve(int n)
{
int i;
memset(num,0,sizeof(num));
for(i=1;i<=n;i++)
num[i]=BF(keyword[i],str);
for(i=1;i<=n;i++)
{
if(num[i])
printf("%s: %d\n",keyword[i],num[i]);
}
}
void KMP_Solve(int n)
{
int i;
memset(num,0,sizeof(num));
for(i=1;i<=n;i++)
num[i]=KMP_count(keyword[i],str);
for(i=1;i<=n;i++)
{
if(num[i])
printf("%s: %d\n",keyword[i],num[i]);
}
}
void DFA_Solve(int n)
{
int i;
head=tail=0;
DFANode *root=new DFANode();
for(i=1;i<=n;i++)
{
insert(keyword[i],root,i);
}
build_ac_automation(root);
memset(num,0,sizeof(num));
query(str,root,num);
for(i=1;i<=n;i++)
{
if(num[i])
printf("%s: %d\n",keyword[i],num[i]);
}
}
int main()
{
int n,i;
double dur;
FILE *fp_R;
FILETIME begin,end;
fp_R=fopen("in.txt","r");
while(~fscanf(fp_R,"%d",&n))
{
for(i=1;i<=n;i++)
fscanf(fp_R,"%s",keyword[i]);
fscanf(fp_R,"%s",str);
GetSystemTimeAsFileTime(&begin);
BF_Solve(n);
GetSystemTimeAsFileTime(&end);
dur=(end.dwLowDateTime-begin.dwLowDateTime);
cout<<"BF算法运行耗时:"<<dur<<endl;
GetSystemTimeAsFileTime(&begin);
KMP_Solve(n);
GetSystemTimeAsFileTime(&end);
dur=(end.dwLowDateTime-begin.dwLowDateTime);
cout<<"KMP算法运行耗时:"<<dur<<endl;
GetSystemTimeAsFileTime(&begin);
DFA_Solve(n);
GetSystemTimeAsFileTime(&end);
dur=(end.dwLowDateTime-begin.dwLowDateTime);
cout<<"DFA算法运行耗时:"<<dur<<endl;
}
fclose(fp_R);
return 0;
}
参考文献
[1]肖汉.软件测试.电子工业出版社.2013.
[2](美)Glenford J.Myers 等.软件测试的艺术. 机械工业出版社.2005.
[3](美)Rex Black .测试流程管理. 北京大学出版社.2001.
这篇文章是那时编译原理的作业,由于不了解软件测试,参考了很多文献,在此表示感谢!同时欢迎大家斧正!