游戏软件测试
主讲人:徐丽
可测性可测性
要有一套统一打印函数及详细的说明要有一套统一打印函数及详细的说明
在在同同一一项项目目组组或或产产品品组组内内,,要要有有一一套套统统一一的的为为集集
成成测测试试与与系系统统联联调调准准备备的的调调测测开开关关及及相相应应打打印印函函
数,并且要有详细的说明。数,并且要有详细的说明。
说明:本规则是针对项目组或产品组的。说明:本规则是针对项目组或产品组的。
信息串的格式要统一信息串的格式要统一
在在同同一一项项目目组组或或产产品品组组内内,,调调测测打打印印出出的的信信息息串串
的的格格式式要要有有统统一一的的形形式式。。信信息息串串中中至至少少要要有有所所在在
模块名(或源文件名)及行号。模块名(或源文件名)及行号。
说明:统一的调测信息格式便于集成测试。说明:统一的调测信息格式便于集成测试。
选择恰当的测试点
编程的同时要为单元测试选择恰当的测试点,并
仔细构造测试代码、测试用例,同时给出明确的
注释说明。测试代码部分应作为(模块中的)一
个子模块,以方便测试代码在模块中的安装与拆
卸(通过调测开关)。
说明:为单元测试而准备。
集成测试/系统联调之前的准备
在进行集成测试/系统联调之前,要构造好测试环
境、测试项目及测试用例,同时仔细分析并优化
测试用例,以提高测试效率。
说明:好的测试用例应尽可能模拟出程序所遇到
的边界值、各种复杂环境及一些极端情况等。
使用断言来发现软件问题使用断言来发现软件问题
使用断言来发现软件问题,提高代码可测性。使用断言来发现软件问题,提高代码可测性。
说说明明::断断言言是是对对某某种种假假设设条条件件进进行行检检查查((可可理理
解解为为若若条条件件成成立立则则无无动动作作,,否否则则应应报报告告)),,它它可可
以以快快速速发发现现并并定定位位软软件件问问题题,,同同时时对对系系统统错错误误进进
行行自自动动报报警警。。断断言言可可以以对对在在系系统统中中隐隐藏藏很很深深,,用用
其其它它手手段段极极难难发发现现的的问问题题进进行行定定位位,,从从而而缩缩短短软软
件件问问题题定定位位时时间间,,提提高高系系统统的的可可测测性性。。实实际际应应用用
时,可根据具体情况灵活地设计断言。时,可根据具体情况灵活地设计断言。
使用断言检查非法情况使用断言检查非法情况
用用断断言言来来检检查查程程序序正正常常运运行行时时不不应应发发生生但但在在调调
测时有可能发生的非法情况。测时有可能发生的非法情况。
断言的正确使用断言的正确使用
不不能能用用断断言言来来检检查查最最终终产产品品肯肯定定会会出出现现且且必必须须
处理的错误情况。处理的错误情况。
说说明明::断断言言是是用用来来处处理理不不应应该该发发生生的的错错误误情情况况
的的,,对对于于可可能能会会发发生生的的且且必必须须处处理理的的情情况况要要写写防防
错错程程序序,,而而不不是是断断言言。。如如某某模模块块收收到到其其它它模模块块或或
链链路路上上的的消消息息后后,,要要对对消消息息的的合合理理性性进进行行检检查查,,
此过程为正常的错误检查,不能用断言来实现。此过程为正常的错误检查,不能用断言来实现。
对较复杂的断言加上明确的注释对较复杂的断言加上明确的注释
说明:为复杂的断言加注释,可澄清断言含义并说明:为复杂的断言加注释,可澄清断言含义并
减少不必要的误用。减少不必要的误用。
用断言确认函数的参数用断言确认函数的参数
示例:假设某函数参数中有一个指针,那么使用示例:假设某函数参数中有一个指针,那么使用
指针前可对它检查,如下。指针前可对它检查,如下。
int ExamFun(unsigned char *str)int ExamFun(unsigned char *str)
{ {
EXAM_ASSERT(str != NULL); EXAM_ASSERT(str != NULL); ////用断言检查用断言检查
““假设指针不为空假设指针不为空””这个条件这个条件
... ... //other program code//other program code
} }
确保不使用没有定义的特性或功能确保不使用没有定义的特性或功能
用断言保证没有定义的特性或功能不被使用。用断言保证没有定义的特性或功能不被使用。
示示例例::假假设设某某通通信信模模块块在在设设计计时时,,准准备备提提供供
““无无连连接接””和和““连连接接” ” 这这两两种种业业务务。。但但当当前前的的
版版本本中中仅仅实实现现了了““无无连连接接””业业务务,,且且在在此此版版本本
的的正正式式发发行行版版中中,,用用户户((上上层层模模块块))不不应应产产生生
““连连接接””业业务务的的请请求求,,那那么么在在测测试试时时可可用用断断言言
检查用户是否使用检查用户是否使用““连接连接””业务。业务。
#define EXAM_CONNECTIONLESS 0 //#define EXAM_CONNECTIONLESS 0 //无连接业务无连接业务
#define EXAM_CONNECTION 1 // #define EXAM_CONNECTION 1 // 连接业务连接业务
int MsgProcess(EXAM_MESSAGE *msg)int MsgProcess(EXAM_MESSAGE *msg)
{{
unsigned char service; unsigned char service; /* message service /* message service
class */class */
EXAM_ASSERT(msg != NULL); EXAM_ASSERT(msg != NULL);
service = GetMsgServiceClass(msg); service = GetMsgServiceClass(msg);
EXAM_ASSERT(service != EXAM_CONNECTION); EXAM_ASSERT(service != EXAM_CONNECTION);
// // 假设不使用连接业务假设不使用连接业务
... ... //other program code//other program code
}}
用断言对程序开发环境的假设进行检查
用断言对程序开发环境( OS/Compiler/
Hardware)的假设进行检查。
说明:程序运行时所需的软硬件环境及配置要
求,不能用断言来检查,而必须由一段专门代码
处理。用断言仅可对程序开发环境中的假设及所
配置的某版本软硬件是否具有某种功能的假设进
行检查。如某网卡是否在系统运行环境中配置了,
应由程序中正式代码来检查;而此网卡是否具有
某设想的功能,则可由断言来检查。
对编译器提供的功能及特性假设可用断言检对编译器提供的功能及特性假设可用断言检
查,原因是软件最终产品(即运行代码或机器码)查,原因是软件最终产品(即运行代码或机器码)
与编译器已没有任何直接关系,即软件运行过程与编译器已没有任何直接关系,即软件运行过程
中(注意不是编译过程中)不会也不应该对编译中(注意不是编译过程中)不会也不应该对编译
器的功能提出任何需求。器的功能提出任何需求。
示例:用断言检查编译器的示例:用断言检查编译器的intint型数据占用的内型数据占用的内
存空间是否为存空间是否为22,如下。,如下。
EXAM_ASSERT(sizeof(int) == 2); EXAM_ASSERT(sizeof(int) == 2);
正式软件产品中应把断言及其它调测代正式软件产品中应把断言及其它调测代
码去掉码去掉
正式软件产品中应把断言及其它调测代码去正式软件产品中应把断言及其它调测代码去
掉(即把有关的调测开关关掉)。掉(即把有关的调测开关关掉)。
说明:加快软件运行速度。说明:加快软件运行速度。
不能影响软件实现的功能不能影响软件实现的功能
在在软软件件系系统统中中设设置置与与取取消消有有关关测测试试手手段段,,不不
能对软件实现的功能等产生影响。能对软件实现的功能等产生影响。
说说明明::即即有有测测试试代代码码的的软软件件和和关关掉掉测测试试代代码码
的软件,在功能行为上应一致。的软件,在功能行为上应一致。
减少维护的难度减少维护的难度
用用调调测测开开关关来来切切换换软软件件的的DEBUGDEBUG版版和和正正式式版版,,
而而不不要要同同时时存存在在正正式式版版本本和和DEBUGDEBUG版版本本的的不不同同源源
文件,以减少维护的难度。文件,以减少维护的难度。
确保软件版本在实现功能上的一致性确保软件版本在实现功能上的一致性
软件的软件的DEBUGDEBUG版本和发行版本应该统一维护,版本和发行版本应该统一维护,
不允许分家,并且要时刻注意保证两个版本在实现不允许分家,并且要时刻注意保证两个版本在实现
功能上的一致性。功能上的一致性。
编写代码之前要注意的事项编写代码之前要注意的事项
在编写代码之前,应预先设计好程序调试与测在编写代码之前,应预先设计好程序调试与测
试的方法和手段,并设计好各种调测开关及相应测试的方法和手段,并设计好各种调测开关及相应测
试代码如打印函数等。试代码如打印函数等。
说明:程序的调试与测试是软件生存周期中很说明:程序的调试与测试是软件生存周期中很
重要的一个阶段,如何对软件进行较全面、高率的重要的一个阶段,如何对软件进行较全面、高率的
测试并尽可能地找出软件中的错误就成为很关键的测试并尽可能地找出软件中的错误就成为很关键的
问题。因此在编写源代码之前,除了要有一套比较问题。因此在编写源代码之前,除了要有一套比较
完善的测试计划外,还应设计出一系列代码测试手完善的测试计划外,还应设计出一系列代码测试手
段,为单元测试、集成测试及系统联调提供方便。段,为单元测试、集成测试及系统联调提供方便。
调测开关应分为不同级别和类型调测开关应分为不同级别和类型
调测开关应分为不同级别和类型。调测开关应分为不同级别和类型。
说明:调测开关的设置及分类应从以下几方说明:调测开关的设置及分类应从以下几方
面考虑:针对模块或系统某部分代码的调测;针面考虑:针对模块或系统某部分代码的调测;针
对模块或系统某功能的调测;出于某种其它目的,对模块或系统某功能的调测;出于某种其它目的,
如对性能、容量等的测试。这样做便于软件功能如对性能、容量等的测试。这样做便于软件功能
的调测,并且便于模块的单元测试、系统联调等。的调测,并且便于模块的单元测试、系统联调等。
用断言宣布发生错误用断言宣布发生错误
编写防错程序,然后在处理错误之后可用断编写防错程序,然后在处理错误之后可用断
言宣布发生错误。言宣布发生错误。
程序效率程序效率
编程时要经常注意代码的效率编程时要经常注意代码的效率
说说明明::代代码码效效率率分分为为全全局局效效率率、、局局部部效效率率、、时时
间间效效率率及及空空间间效效率率。。全全局局效效率率是是站站在在整整个个系系统统的的
角角度度上上的的系系统统效效率率;;局局部部效效率率是是站站在在模模块块或或函函数数
角角度度上上的的效效率率;;时时间间效效率率是是程程序序处处理理输输入入任任务务所所
需需的的时时间间长长短短;;空空间间效效率率是是程程序序所所需需内内存存空空间间,,
如如机机器器代代码码空空间间大大小小、、数数据据空空间间大大小小、、栈栈空空间间大大
小等。小等。
提高代码效率提高代码效率
在在保保证证软软件件系系统统的的正正确确性性、、稳稳定定性性、、可可读读性性及及
可测性的前提下,提高代码效率。可测性的前提下,提高代码效率。
说说明明::不不能能一一味味地地追追求求代代码码效效率率,,而而对对软软件件的的
正确性、稳定性、可读性及可测性造成影响。正确性、稳定性、可读性及可测性造成影响。
局部效率与全局效率
局部效率应为全局效率服务,不能因为
提高局部效率而对全局效率造成影响。
提高空间效率
通过对系统数据结构的划分与组织的改
进,以及对程序算法的优化来提高空间效
率。
说明:这种方式是解决软件空间效率的
根本办法。
示例:如下记录学生学习成绩的结构不合理。示例:如下记录学生学习成绩的结构不合理。
typedef unsigned char BYTE;typedef unsigned char BYTE;
typedef unsigned short WORD;typedef unsigned short WORD;
typedef struct STUDENT_SCORE_STRUtypedef struct STUDENT_SCORE_STRU
{{
BYTE name[8];BYTE name[8];
BYTE age;BYTE age;
BYTE sex;BYTE sex;
BYTE class;BYTE class;
BYTE subject;BYTE subject;
float score;float score;
} STUDENT_SCORE;} STUDENT_SCORE;
因因为为每每位位学学生生都都有有多多科科学学习习成成绩绩,,故故如如上上结结构构将将
占占用用较较大大空空间间。。应应如如下下改改进进((分分为为两两个个结结构构)),,
总的存贮空间将变小,操作也变得更方便。总的存贮空间将变小,操作也变得更方便。
typedef struct STUDENT_STRUtypedef struct STUDENT_STRU
{{
BYTE name[8];BYTE name[8];
BYTE age;BYTE age;
BYTE sex;BYTE sex;
BYTE class;BYTE class;
} STUDENT;} STUDENT;
typedef struct STUDENT_SCORE_STRUtypedef struct STUDENT_SCORE_STRU
{{
WORD studentIndex;WORD studentIndex;
BYTE subject;BYTE subject;
float score;float score;
} STUDENT_SCORE;} STUDENT_SCORE;
循环体内工作量最小化循环体内工作量最小化
说说明明::应应仔仔细细考考虑虑循循环环体体内内的的语语句句是是否否可可以以放放
在在循循环环体体之之外外,,使使循循环环体体内内工工作作量量最最小小,,从从而而提提
高程序的时间效率。高程序的时间效率。
示例:如下代码效率不高。示例:如下代码效率不高。
for (ind = 0;ind < MAX_ADD_NUMBER;ind++) for (ind = 0;ind < MAX_ADD_NUMBER;ind++)
{ {
sum += ind;sum += ind;
backSum = sum; backSum = sum; /* backup sum *//* backup sum */
} }
算法的优化算法的优化
仔细分析有关算法,并进行优化。仔细分析有关算法,并进行优化。
改进系统及模块处理输入的方式改进系统及模块处理输入的方式
仔细考查、分析系统及模块处理输入(如事务、仔细考查、分析系统及模块处理输入(如事务、
消息等)的方式,并加以改进。消息等)的方式,并加以改进。
提高程序效率提高程序效率
对模块中函数的划分及组织方式进行分析、优对模块中函数的划分及组织方式进行分析、优
化,改进模块中函数的组织结构,提高程序效率。化,改进模块中函数的组织结构,提高程序效率。
说明:软件系统的效率主要与算法、处理任务说明:软件系统的效率主要与算法、处理任务
方式、系统功能及函数结构有很大关系,仅在代方式、系统功能及函数结构有很大关系,仅在代
码上下功夫一般不能解决根本问题。码上下功夫一般不能解决根本问题。
留心代码效率留心代码效率
编程时,要随时留心代码效率;优化代码时,编程时,要随时留心代码效率;优化代码时,
要考虑周全。要考虑周全。
恰当优化代码提高效率恰当优化代码提高效率
不不应应花花过过多多的的时时间间拼拼命命地地提提高高调调用用不不很很频频繁繁的的
函数代码效率。函数代码效率。
说说明明::对对代代码码优优化化可可提提高高效效率率,,但但若若考考虑虑不不周周
很有可能引起严重后果。很有可能引起严重后果。
慎重使用汇编嵌入方式慎重使用汇编嵌入方式
要要仔仔细细地地构构造造或或直直接接用用汇汇编编编编写写调调用用频频繁繁或或性性
能要求极高的函数。能要求极高的函数。
说说明明::只只有有对对编编译译系系统统产产生生机机器器码码的的方方式式以以及及
硬硬件件系系统统较较为为熟熟悉悉时时,,才才可可使使用用汇汇编编嵌嵌入入方方式式。。
嵌嵌入入汇汇编编可可提提高高时时间间及及空空间间效效率率,,但但也也存存在在一一定定
风险。风险。
提高空间效率提高空间效率
在保证程序质量的前提下,通过压缩代码量、在保证程序质量的前提下,通过压缩代码量、
去掉不必要代码以及减少不必要的局部和全局变去掉不必要代码以及减少不必要的局部和全局变
量,来提高空间效率。量,来提高空间效率。
说明:这种方式对提高空间效率可起到一定说明:这种方式对提高空间效率可起到一定
作用,但往往不能解决根本问题。作用,但往往不能解决根本问题。
在多重循环中,应将最忙的循环放在最在多重循环中,应将最忙的循环放在最
内层内层
说明:减少说明:减少CPUCPU切入循环层的次数。切入循环层的次数。
示例:如下代码效率不高。示例:如下代码效率不高。
for (row = 0; row < 100; row++)for (row = 0; row < 100; row++)
{ {
for (col = 0; col < 5; col++)for (col = 0; col < 5; col++)
{{
sum += a[row][col];sum += a[row][col];
}}
}}
可以改为如下方式,以提高效率。可以改为如下方式,以提高效率。
for (col = 0; col < 5; col++)for (col = 0; col < 5; col++)
{{
for (row = 0; row < 100; row++)for (row = 0; row < 100; row++)
{{
sum += a[row][col];sum += a[row][col];
}}
}}
尽量减少循环嵌套层次尽量减少循环嵌套层次
避免循环体内含判断语句避免循环体内含判断语句
避避免免循循环环体体内内含含判判断断语语句句,,应应将将循循环环语语句句置置
于判断语句的代码块之中。于判断语句的代码块之中。
说说明明::目目的的是是减减少少判判断断次次数数。。循循环环体体中中的的判判
断断语语句句是是否否可可以以移移到到循循环环体体外外,,要要视视程程序序的的具具
体体情情况况而而言言,,一一般般情情况况,,与与循循环环变变量量无无关关的的判判
断语句可以移到循环体外,而有关的则不可以。断语句可以移到循环体外,而有关的则不可以。
示例:如下代码效率稍低。示例:如下代码效率稍低。
for (ind = 0; ind < MAX_RECT_NUMBER; for (ind = 0; ind < MAX_RECT_NUMBER;
ind++)ind++)
{{
if (dataType == RECT_AREA)if (dataType == RECT_AREA)
{{
areaSum += rectArea[ind];areaSum += rectArea[ind];
}}
elseelse
{{
rectLengthSum += rect[ind].length; rectLengthSum += rect[ind].length;
rectWidthSum += rect[ind].width; rectWidthSum += rect[ind].width;
}}
}}
因为判断语句与循环变量无关,故可如下改进,以减少因为判断语句与循环变量无关,故可如下改进,以减少
判断次数。判断次数。
if (dataType == RECT_AREA)if (dataType == RECT_AREA)
{{
for (ind = 0; ind < MAX_RECT_NUMBER; ind++) for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{ {
areaSum += rectArea[ind]; areaSum += rectArea[ind];
} }
}}
elseelse
{{
for (ind = 0; ind < MAX_RECT_NUMBER; ind++) for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{ {
rectLengthSum += rect[ind].length; rectLengthSum += rect[ind].length;
rectWidthSum += rect[ind].width; rectWidthSum += rect[ind].width;
} }
}}
用乘法或其它方法代替除法用乘法或其它方法代替除法
尽量用乘法或其它方法代替除法,特别是浮尽量用乘法或其它方法代替除法,特别是浮
点运算中的除法。点运算中的除法。
说明:浮点运算除法要占用较多说明:浮点运算除法要占用较多CPUCPU资源。资源。
示例:如下表达式运算可能要占较多示例:如下表达式运算可能要占较多CPUCPU资源资源。。
#define PAI PAI
radius = circleLength / (2 * PAI);radius = circleLength / (2 * PAI);
应如下把浮点除法改为浮点乘法。应如下把浮点除法改为浮点乘法。
#define PAI_RECIPROCAL (1 / ) #define PAI_RECIPROCAL (1 / ) // //
编译器编译时,将生成具体浮点数编译器编译时,将生成具体浮点数
radius = circleLength * PAI_RECIPROCAL / radius = circleLength * PAI_RECIPROCAL /
2; 2;
不要一味追求紧凑的代码不要一味追求紧凑的代码
说明:因为紧凑的代码并不代表高效的机器码。说明:因为紧凑的代码并不代表高效的机器码。
宏
用宏定义表达式时,要使用完备的括号用宏定义表达式时,要使用完备的括号
示例:如下定义的宏都存在一定的风险。示例:如下定义的宏都存在一定的风险。
#define RECTANGLE_AREA( a, b ) a * b#define RECTANGLE_AREA( a, b ) a * b
#define RECTANGLE_AREA( a, b ) (a * b)#define RECTANGLE_AREA( a, b ) (a * b)
#define RECTANGLE_AREA( a, b ) (a) * (b)#define RECTANGLE_AREA( a, b ) (a) * (b)
正确的定义应为:正确的定义应为:
#define RECTANGLE_AREA( a, b ) ((a) * #define RECTANGLE_AREA( a, b ) ((a) *
(b))(b))
将宏所定义的多条表达式放在大括号中将宏所定义的多条表达式放在大括号中
示例:下面的语句只有宏的第一条表达式被执行。示例:下面的语句只有宏的第一条表达式被执行。
为了说明问题,为了说明问题,forfor语句的书写稍不符规范。语句的书写稍不符规范。
#define INTI_RECT_VALUE( a, b )\#define INTI_RECT_VALUE( a, b )\
a = 0;\a = 0;\
b = 0;b = 0;
for (index = 0; index < RECT_TOTAL_NUM; index++)for (index = 0; index < RECT_TOTAL_NUM; index++)
INTI_RECT_VALUE(rect[index].a, rect[index].b);INTI_RECT_VALUE(rect[index].a, rect[index].b);
正确的用法应为:正确的用法应为:
#define INTI_RECT_VALUE( a, b )\#define INTI_RECT_VALUE( a, b )\
{\{\
a = 0;\a = 0;\
b = 0;\b = 0;\
}}
for (index = 0; index < RECT_TOTAL_NUM; index++)for (index = 0; index < RECT_TOTAL_NUM; index++)
{ {
INTI_RECT_VALUE(rect[index].a, rect[index].b);INTI_RECT_VALUE(rect[index].a, rect[index].b);
}}
使用宏时,不允许参数发生变化
示例:如下用法可能导致错误。
#define SQUARE( a ) ((a) * (a))
int a = 5;
int b;
b = SQUARE(a++);
正确的用法是:
b = SQUARE(a);
a++;