目前,国内软件项目的验收管理没有可参照的强制内容及标准,这样,对于软件项目验收来说,存在很大分歧和不确定性,也为全程软件质量保障安插了隐患。
为此,我们在参考了大量文献基础上,结合实际验收管理经验,总结了软件验收管理的部分工作内容,不足之处,欢迎指正。
软件项目验收管理工作内容:
一、软件验收管理的准备工作
1、对完整产品的运行进行确认;
2、证实系统已满足合同规定的条件及需求说明书中对系统功能和性能的要求;
3、整理验收需要文档列表、系统软硬件配置清单:
(1)软件工程立项批准文件
(2)项目验收申请报告;
(3)招标书
(4)投标书
(5)中标通知书
(6)合同(含预算表)
(7)软件需求说明书;
(8)概要设计说明书;
(9)数据及数据库设计要求说明书;
(10)详细设计说明书;
(11)操作手册;
(12)用户手册
(13)项目用户评价过程意见;
(14)软件接口规范;
(15)原代码或安装盘;
(16)专家组要求的其他材料
4、制定人员培训和技术支持的计划。
二、软件系统验收申请报告
1、卖方向买房提出软件系统验收申请报告,说明申请系统验收的准备情况和系统所具备的验收条件。系统验收申请报告,必须按合同书的有关规定,交付有关产品资料,其中包括系统设备及系统软件配置清单、文档、技术总结报告和测试分析报告等,系统验收申请报告应有卖方的技术负责人签字。
2、买方的经办人或第三方验收管理机构必须了解验收系统的功能、性能和系统配置与文档等方面的要求,掌握合同书中规定的系统验收条款,对卖方提交的系统 验收申请报告进行审查,提出处理意见;买方技术负责人经审查后,在申请报告上签字并对卖方的申请进行答复;买方将按合同有关条款做好系统验收的全部准备工 作,包括对测试用例、测试数据、测试过程和测试环境的准备。
三、制定系统验收计划
在软件系统验收活动进行之前,应制定一套完整的系统验收测试计划。该计划要由买方认可,而且还要包括一些由买方提供的测试方案,该计划应包括系统验收工作的活动程序、验收测试要求、技术条件、设备资源、验收准则、工作人员的组成以及日程安排等内容。
四、制定组织机构及人员组成
1、成立专门的软件系统验收委员会,作为系统验收的组织机构,委员会设主任委员若干,并由该委员会组织成立系统验收测试组、技术组和文档审查组,配备若干测试员和记录员。
2、验收委员会由买方代表、特邀专家或第三方测试机构专家及最终买方代表组成,必要时,也可吸收卖方代表参加。特邀专家必须是行业信息领域的权威,熟悉国内外该领域技术发展的状况。
五、规范验收委员会的任务与权限
1、验收委员会的任务
审定系统验收计划;
听取卖方的《技术总结报告》和《测试分析报告》;
判定所验收的系统是否符合合同及系统需求说明书的要求;
审定验收测试计划;
组织验收测试和进行系统验收评审,并形成系统验收报告;
监督系统验收后的产品移交。
2、验收委员会的权限
有权要求卖方和买方对系统开发过程中的有关问题进行说明,提出质疑并要求作出解释;
在验收过程中,协调卖方和买方之间可能发生的纠纷;
决定系统是否通过验收。
六、验收记录
验收工作的全部过程必须详细记录,记录验收过程中验收委员会,提出的所有问题与建议,卖方的解答和验收委员会对被验收系统的评价,并形成文件供评审时查阅及存档。
七、制定软件验收测试计划
软件系统的验收测试是系统验收活动中最关键的步骤,被验收的系统必须满足合同条款与系统需求说明书中规定的要求。
测试计划及程序包括下列几项:
1、测试计划及程序的目的;
2、各项功能测试所需输入的数据;
3、测试结果记录的说明;
4、观察、测试结果的设备、工具及程序;
有关的测试结果要以书面报告的形式由卖方向买方提交,
内容包括:
(1)测试的系统功能;
(2)为纠正系统缺点需做的变动;
(3)为提高系统性能提出的建议。
八、软件系统验收 1、系统设备验收:
验收委员会的技术组根据卖方提供的系统设备、计算机网络设备、和系统软件、数据库和工具软件配置清单,并对照合同或项目建议书的有关规定,检查这些设备及其各项性能指标是否符合要求并将审查结果写进《系统设备验收报告》。
2、验收测试前的检查:
验收委员会在审定系统验收测试计划时,要检查测试环境是否符合要求,检查全部测试项目的测试用例是否准备好,有关测试人员是否全部到位。
3、系统演示:
卖方应向验收委员会演示被验收系统的全部买方界面、系统包括的主要功能、性能,以证明系统实现的功能与合同要求一致。通过演示活动让验收委员会 成员对系统有一个直观和概括的了解。验收委员可现场选用实例对被验收系统时行演示考核,以证实与系统需求的一致性、程序和文档的一致性。
4、验收测试:
验收测试组应按系统验收测试计划对系统进行:1)环境验收测试;2)可靠性验收测试;3)维护性验收测试;4)功能验收测试;5)稳定性验收测 试;6)性能验收测试;7)仿真测试;8)可移植性、兼容性、可靠性、错误的恢复功能等测试;9)文档验收。验收标准:1) 测试用例不通过数的比例< 1.5 %;2) 不存在错误等级为1 的错误;3) 不存在错误等级为2 的错误;4) 错误等级为3 的错误数量≤ 5;5) 所有提交的错误都已得到更正。
测试员按分工分别对被验收系统进行逐项测试,并详细记录每一项测试结果,将这些结果分别与预期的结果对照分析,然后写出《系统验收测试报告》,该报告将作为验收委员会评价系统的主要依据,也是买方确定是否接收该系统的主要依据。
5、系统验收评审:
在软件验收测试完成以后,验收委员会应及时主持评审会,听取有关报告和审议验收结果,并对系统作出综合评价。评审会的议程如下:
听取卖方的《测试分析报告》和《技术总结报告》;
听取系统验收测试组的《系统设备验收报告》、文档审查组的《文档审查报告》及测试组的《系统验收测试报告》;
按以下的验收准则对系统进行评价:
(1)系统是否满足用户信息系统要实现的目标。
(2)系统采用的技术和实现方案是否做到可靠、先时、灵活、实用。
(3)设备选型是否达到以下要求:
选用目前在国际上技术先进、性能优异的设备,确保系统的先进性和可靠性。
所选用的设备不仅能满足目前用户信息系统业务在功能和性能上的要求,而且具有良好的开放性和扩充性。
所选用的应用开发平台和开发工具先时、简便、有效。
便于与其他系统的衔接,实现资源共享。
(4)运行系统的可靠性是系统建设的首要出发点。因此,要求卖方提供高可靠性的产品和技术,确保系统的安全和可靠。要求系统具有较强的容错能力,使系统不易崩溃。
(5)关键系统设备与数据备份的设施是否达到安全可靠。
验收委员会应进行认真地讨论,对被验收的系统给出实事求是的评价,内容包括系统的先进性、功能性、可靠性和安全保密性。最后由验收委员会进行表决,决定系统是否通过验收。
6、软件系统验收报告:
在验收评审后,验收委员会应写出“系统验收报告”,详尽地记录验收中对系统的评价及验收意见,尤其要明确系统在验收中发现的问题和缺陷,以及需 要改进的意见和卖方对些所作的承诺,验收委员会全体成员在验收报告上签字,根据验收委员会表决情况,由验收委员会主任在验收报告上签署验收意见。
验收结论分为以下两种;
a.通过。表示同意验收的委员超过三分之二;
b.不通过。表示同意验收的委员不超过三分之一。
如果系统验收不能通过,验收委员会将根据合同书的规定与供需双方协商处理意见,可能的结果是:要求卖方限期完成开发任务,重新提出验收申请或者 终止合同。系统验收通过后,要确定系统进入试运行的时间结束时间,明确卖方在试运行期间要解决的遗留问题以及改进系统的意见,对此卖方的代表要作出承诺。
7、软件项目产品移交:
系统通过验收以后,验收委员会的技术组和文档审查组应分别卖方提供的系统设备清单和文档资料清单进行验收,逐项核实后移交给买方。移交结束后形成产品移交文件,该文件应包括以下内容:
a.移交产品清单
卖方向买方移交的产品清单分别包括系统构成的硬件和软件部分。硬件部分包括卖方在应和开发或系统配置时,受买方委托代购的各种计算机与外部设 备,移交清单中应包括设备名称、单价、数量、供货厂商、保修期限;软件部分包括系统软件、数据库软件等软件的名称、单价、供货厂商。
b.移交的时间、地点、收授人签字。
版权声明:本文出自山东省软件评测中心 张凯丽,51Testing软件测试网原创出品,未经明确的书面许可,任何人或单位不得对本文进行复制、转载或镜像,否则将追究法律责任。
http://www.51testing.com
客户在进行短信服务这个业务申请时,需要填写一些基本信息,然后根据这些信息判断这个用户是否已经存在于业务系统中。因为网上服务和业务系统两个项目物理隔离,而且网上数据库保存的客户信息不全,所以判断需要把数据交换到业务系统,在业务系统中判断。
解决方式是通过存储过程,以前也了解过存储过程,但没使用到项目中。不过经过一番努力最后还是完成了,期间遇到了一些困难,特写此文让对DB2存储过程还不熟悉的童鞋避免一些无谓的错误。
DROP PROCEDURE "PLName"
@
CREATE PROCEDURE "PLName"(--存储过程名字
IN IN_ID BIGINT , --以下全是输入参数
IN IN_ENTNAME VARCHAR(200) ,
IN IN_REGNO VARCHAR(50),
IN IN_PASSWORD VARCHAR(20),
IN IN_LEREP VARCHAR(300),
IN IN_CERTYPE CHARACTER(1),
IN IN_CERNO VARCHAR(50),
IN IN_LINKMAN VARCHAR(50),
IN IN_SEX CHARACTER(1),
IN IN_MOBTEL VARCHAR(30),
IN IN_REQDATE TIMESTAMP,
IN IN_REMITEM VARCHAR(300),
IN IN_STATE CHARACTER(1),
IN IN_TIMESTAMP TIMESTAMP
)
BEGIN
declare V_RESULT BIGINT; --声明变量
DELETE FROM TableNameA WHERE ID = IN_ID;
SET V_RESULT = NULL; --为变量赋值
--检查用户输入的信息是否合法
select b.id INTO V_RESULT from TableNameB b,TableNameC c where 正常的判断条件
if(V_RESULT IS NOT NULL) then ---如果合法,执行下面的insert语句
INSERT INTO TableNameA(ID,ENTNAME,REGNO,PASSWORD,LEREP,CERTYPE,CERNO,LINKMAN,SEX,MOBTEL,REQDATE,REMITEM,STATE,TIMESTAMP)
VALUES(IN_ID,IN_ENTNAME,IN_REGNO,IN_PASSWORD,IN_LEREP,IN_CERTYPE,IN_CERNO,IN_LINKMAN,IN_SEX,IN_MOBTEL,IN_REQDATE,IN_REMITEM,IN_STATE,IN_TIMESTAMP);
end if;
commit;
END
@
功能说明:
调用存储过程时会传入一些值(IN输入参数),然后根据传入的值查询数据库(select语句),根据查询结果执行操作(添加、删除、更新)
有两种方式执行写好的存储过程:
1.拷贝到DB2客户端工具中直接执行
特别注意:执行时将

改成@,之前很多错误都和它有关,比如:“该命令被当作 SQL
语句来处理,因为它不是有效的命令行处理器命令”正是这个问题花费了很长时间,严重影响心情
2.将上面的语句保存为test.db2文件放到任意目录下(比如D盘根目录),然后在cmd输入db2cmd 然后输入db2 -td@ -vf D:\test.db2即可
执行后就可以测试存储过程写的是否正确
直接写sql:
call PLName(存储过程名字) (IN_ID,IN_ENTNAME,IN_REGNO,IN_PASSWORD,IN_LEREP,IN_CERTYPE,IN_CERNO,IN_LINKMAN,IN_SEX,IN_MOBTEL,IN_REQDATE,IN_REMITEM,IN_STATE,IN_TIMESTAMP对应的值)
以上就是我今天所用到的存储过程,功能非常简单,比较复杂的操作也在摸索阶段,有什么疑问大家可以随时交流。
CREATE PROCEDURE KJZB.ZQINVEST_JT(out returnCode Integer,out
errorMsg varchar(255))
LANGUAGE SQL
BEGIN
declare sql_code integer default 0;
declare SQLSTATE char(5) default '00000';
declare SQLCODE integer default 0;
declare sqlMsg varchar(200) default '';
declare stmt varchar(1024);
declare vCurdate varchar(20); -- 当天日期
declare vCurYMd varchar(6); -- 当天所在的年月
declare vCurDay varchar(2) ; --所在天
declare maxday integer default 30; -- 每个月按30天算
declare fday integer; -- 实际天数
declare lastday integer; -- 到期日的天数
declare firstday integer; -- 购买月的天数
declare firstmonth integer; -- 购买的月份
declare lastmonth integer; -- 到期日的月份
declare firstyear integer; -- 购买日的年份
declare lastyear integer; -- 到期年份
declare disday integer; -- 间隔天数
declare dismonth integer; -- 间隔月份
declare disyear integer; -- 间隔年份
declare vLsh varchar(32); -- 流水号
declare vjgh varchar(8); -- 机构码
declare ywzh varchar(32); -- 业务帐号
declare vzh varchar(32); -- 投资账号
declare vJgbm varchar(8) default '32022300'; -- 投资帐号对应的机构(默认为清算中心)
declare vDfJgbm varchar(8); -- 对方机构号
declare gmrq varchar(8); -- 购买日期
declare dqrq varchar(8); -- 到期日期
declare zqpz varchar(1); -- 债券品种
declare hth varchar(16); -- 合同号
declare fxfs varchar(1); -- 付息方式
declare ywlx varchar(3) default '888'; -- 业务类型(默认业务类型 888)
declare tzqx varchar(1); -- 投资期限
declare ysjlxkmkzz varchar(7); -- 应收(计)利息科目控制字
declare tzsykmmkzz varchar(7); -- 投资收益科目控制字
declare yjkmkzz varchar(7) default '142112'; -- 溢价科目控制字 长期投资(溢价142112)
declare zjkmkzz varchar(7) default '142113'; -- 折价科目控制字 长期投资(折价142113)
declare fykmkzz varchar(7) default '142111'; -- 相关费用科目控制字 长期投资(相关费用 142111)
declare zid varchar(32) default '0'; -- id 债券投资ID
declare vId varchar(32); -- id 债券流水ID
declare yslx double; -- 应收利息 (面值 * 票面利率 (年) /360 * 当月实际天数)
declare yjlx double; -- 应计利息 (面值 * 票面利率 (年) /360 * 当月实际天数)
declare ysjlx double; -- 应收计利息
declare pmje double; -- 票面金额
declare pmll double; -- 票面利率
declare tzsy DECIMAL(12, 2); -- 投资收益 ( 应收(计)利息 - 每月摊销的溢价 - 每月摊销相关费用 )
declare txyj double; -- 月摊销溢价 ( 溢价/(到期日 - 购买日 + 1) * 当月实际天数 )
declare txzj double; -- 月摊销折价 ( 折价/(到期日 - 购买日 + 1) * 当月实际天数 )
declare txfy double; -- 月摊销费用 (相关费用)/(到期日 - 购买日 + 1) * 当月实际天数
declare yj double default 0.0; -- 溢价
declare zj double default 0.0; -- 折价
declare fy double default 0.0; -- 相关费用
declare zqtz cursor for s1; 建立游标
-- 声明异常
DECLARE CONTINUE HANDLER FOR NOT FOUND,SQLEXCEPTION,SQLWARNING
begin
set sql_code = SQLCODE;
set returnCode = sql_code;
end;
set vCurdate = (select char(current_date) from sysibm.sysdummy1);
set vCurdate = replace(vCurdate,'-','');
set vCurYMd = substr(vCurdate,1,6);
set vCurDay = substr(vCurdate,7,2);
-- 读取还没到期的长期债券投资信息
set stmt = ' select JGBM,ZH,HTH,ZQPZ,PMJE,LL,GMRQ,DQR,YSLX,YJLX,YJ,ZJ,FY,FXFS,id,cast(substr(GMRQ,7,2) as integer),cast(substr(DQR,7,2) as integer),cast(substr(GMRQ,5,2) as integer),cast(substr(DQR,5,2) as integer),cast(substr(GMRQ,1,4) as integer),cast(substr(DQR,1,4) as integer) from kjzb.zqinvest where substr(dqr,1,6) >=''' || vCurYMd || ''' and tzqx = ''2'' and tzzt <> ''2''';
prepare s1 from stmt;
open zqtz;
fetch zqtz into vDfJgbm,vzh,hth,zqpz,pmje,pmll,gmrq,dqrq,yslx,yjlx,yj,zj,fy,fxfs,zid,firstday,lastday,firstmonth,lastmonth,firstyear,lastyear;
while sql_code <> 100 do
-- 如果是31日购买的,第一个月不计提
if ( firstday = 31 and substr(gmrq,1,6) = vCurYMd ) then
goto EXIT;
end if;
-- 如果是01日到期的,最后一个月不计提
if ( lastday = 1 and substr(dqrq,1,6) = vCurYMd ) then
goto EXIT;
end if;
-- 间隔年份
if ( firstyear = lastyear ) then
set disyear = 0;
else
set disyear = lastyear - firstyear - 1;
end if;
-- 同年
if ( disyear = 0 ) then
-- 下个月或同月(业务逻辑上不会发生,但程序上要判断防止业务人员误操作)
if ( lastmonth - firstmonth <= 1 ) then
set dismonth = 0;
else
set dismonth = lastmonth - firstmonth - 1;
end if;
else
-- 不在同一年,间隔月份 = 第一年的月份 + 中间年的月份 + 最后一年的月份
set dismonth = ( 12 - firstmonth ) + ( disyear * 12 ) + ( lastmonth - 1 ) ;
end if;
-- 初始化摊销溢价、摊销折价、摊销费用
set txyj = 0.0;
set txzj = 0.0;
set txfy = 0.0;
if fxfs = '5' then -- 根据付息方式来计应计利息还是应收利息科目控制字
if zqpz = '1' then
set ysjlxkmkzz = '142107'; -- 应计利息 国债 142107
set tzsykmmkzz = '514111'; -- 投资收益(债券利息收入-国债利息收入) 514111
end if;
if zqpz = '2' then
set ysjlxkmkzz = '142108'; -- 应计利息 金融债 142108
set tzsykmmkzz = '514112'; -- 投资收益(债券利息收入-金融债券利息收入) 514112
end if;
if zqpz = '3' then
set ysjlxkmkzz = '142109'; -- 应计利息 公司债 142109
set tzsykmmkzz = '514113'; -- 债券利息收入-企业债券利息收入 514113
end if;
if zqpz = '4' then
set ysjlxkmkzz = '142110'; -- 应计利息 其他债券 142110
set tzsykmmkzz = '514114'; -- 债券利息收入-其他债券利息收入 514114
end if;
else
if zqpz = '1' then
set ysjlxkmkzz = '132102'; -- 应收利息 国债 132102
set tzsykmmkzz = '514111'; -- 投资收益(债券利息收入-国债利息收入) 514111
end if;
if zqpz = '2' then
set ysjlxkmkzz = '132103'; -- 应收利息 金融债 132103
set tzsykmmkzz = '514112'; -- 投资收益(债券利息收入-金融债券利息收入) 514112
end if;
if zqpz = '3' then
set ysjlxkmkzz = '132104'; -- 应收利息 公司债 132104
set tzsykmmkzz = '514113'; -- 债券利息收入-企业债券利息收入 514113
end if;
if zqpz = '4' then
set ysjlxkmkzz = '132105'; -- 应收利息 其他债券 132105
set tzsykmmkzz = '514114'; -- 债券利息收入-其他债券利息收入 514114
end if;
end if;
if ( substr(dqrq,1,6) = vCurYMd ) then -- 最后一个月计提
set fday = lastday - 1;
else
if ( substr(gmrq,1,6) = vCurYMd ) then -- 第一个月计提
set fday = 30 - firstday + 1;
else
set fday = maxday; -- 平常按30天算
end if;
end if;
-- 实际天数 = 第一个月的天数 + 间隔月份 * 30 + 最后一个月的天数(到期日不算)
set disday = ( 30 - firstday + 1) + ( dismonth * 30 ) + ( lastday - 1 );
-- 记提公式:面值*票面利率(年)/ 360*当月实际天数
set ysjlx = pmje * (pmll / 100) /360 * fday;
-- 投资收益公式 : 应收(计)利息 ( + 月摊销折价)— 月摊销溢价 - 月摊销费用
set tzsy = ysjlx;
-- 设计思想为如果有溢价,则应该没有折价
if yj <> 0.0 then
set txyj = yj /disday * fday;
set tzsy = tzsy - txyj;
else
if zj <> 0.0 then
set txzj = zj/disday* fday;
set tzsy = tzsy + txzj;
end if;
if fy <> 0.0 then
set txfy = fy /disday * fday;
set tzsy = tzsy - txfy;
end if;
--借: 1321 应收利息[按月计提金额]
set vId = kjzb.getvId();
set vLsh = kjzb.getLsh(32); -- 获取流水号
set vjgh = kjzb.getJgh(vzh); -- 获取机构号
set ywzh = kjzb.getZh(vjgh,ysjlxkmkzz); -- 应收(计)利息账号
if ywzh is null then
set sqlMsg = '债券记提走帐时找不到指定机构['||vjgh||']和科目控制字为['|| ysjlxkmkzz ||']的帐号';
set errorMsg = sqlMsg;
goto ERROR_RETURN;
end if;
insert into kjzb.FLLSB values(vId,vJgbm,ysjlxkmkzz,vLsh,hth,ywzh,'借',ysjlx,'','0000',vCurdate,ywlx);
if sql_code <> 0 then
set sqlMsg = '债券走帐插入会计分录出错';
set errorMsg = sqlMsg;
goto ERROR_RETURN;
end if;
-- 借: 1421 长期投资(折价)[按月计提金额]
if zj <> 0.0 then
set vId = kjzb.getvId();
set ywzh = kjzb.getZh(vjgh,zjkmkzz); -- 长期投资(折价)账号
if ywzh is null then
set sqlMsg = '债券记提走帐时找不到指定机构['||vjgh||']和科目控制字为['|| zjkmkzz ||']的帐号';
set errorMsg = sqlMsg;
goto ERROR_RETURN;
end if;
insert into kjzb.FLLSB values(vId,vJgbm,zjkmkzz,vLsh,hth,ywzh,'借',txzj,'','0000',vCurdate,ywlx);
if sql_code <> 0 then
set sqlMsg = '债券走帐插入会计分录出错';
set errorMsg = sqlMsg;
goto ERROR_RETURN;
end if;
end if;
-- 贷: 5141 投资收益 应收(计)利息-每月摊消的溢价-每月摊消相关费用
set vId = kjzb.getvId();
set ywzh = kjzb.getZh(vjgh,tzsykmmkzz); -- 投资收益账号
if ywzh is null then
set sqlMsg = '债券记提走帐时找不到指定机构['||vjgh||']和科目控制字为['|| tzsykmmkzz ||']的帐号';
set errorMsg = sqlMsg;
goto ERROR_RETURN;
end if;
insert into kjzb.FLLSB values(vId,vJgbm,tzsykmmkzz,vLsh,hth,ywzh,'贷',tzsy,'','0000',vCurdate,ywlx);
if sql_code <> 0 then
set sqlMsg = '债券走帐插入会计分录出错';
set errorMsg = sqlMsg;
goto ERROR_RETURN;
end if;
-- 贷: 1421 长期投资(溢价)[按月摊消]每月摊消的溢价(溢价/(到期日-购买日+1) *当月实际天数)
if yj <> 0.0 then
set vId = kjzb.getvId();
set ywzh = kjzb.getZh(vjgh,yjkmkzz); -- 长期投资(溢价)账号
if ywzh is null then
set sqlMsg = '债券记提走帐时找不到指定机构['||vjgh||']和科目控制字为['|| yjkmkzz ||']的帐号';
set errorMsg = sqlMsg;
goto ERROR_RETURN;
end if;
insert into kjzb.FLLSB values(vId,vJgbm,yjkmkzz,vLsh,hth,ywzh,'贷',txyj,'','0000',vCurdate,ywlx);
if sql_code <> 0 then
set sqlMsg = '债券走帐插入会计分录出错';
set errorMsg = sqlMsg;
goto ERROR_RETURN;
end if;
end if;
-- 贷: 1421 长期投资(相关费用)[按月摊消金额] 每月摊消相关费用(相关费用/(到期日-购买日+1) *当月实际天数)
if (fy <> 0.0) then
set vId = kjzb.getvId();
set ywzh = kjzb.getZh(vjgh,fykmkzz); -- 长期投资(相关费用)账号
if ywzh is null then
set sqlMsg = '债券记提走帐时找不到指定机构['||vjgh||']和科目控制字为['|| fykmkzz ||']的帐号';
set errorMsg = sqlMsg;
goto ERROR_RETURN;
end if;
insert into kjzb.FLLSB values(vId,vJgbm,yjkmkzz,vLsh,hth,ywzh,'贷',txfy,'','0000',vCurdate,ywlx);
if sql_code <> 0 then
set sqlMsg = '债券走帐插入会计分录出错';
set errorMsg = sqlMsg;
goto ERROR_RETURN;
end if;
end if;
-- 对 kjzb.zqinvest 表的以下字段进行更新
-- yjtyjlx 【已计提应计利息】
-- yjtyslx 【已计提应收利息】
-- ytxyj 【已摊销溢价】
-- ytxzj 【已摊销折价】
if fxfs = '5' then
update kjzb.zqinvest set yjtyjlx = yjtyjlx + ysjlx,ytxyj = ytxyj + txyj,ytxzj = ytxzj + txzj,ytxfy = ytxfy + txfy where id = zid;
if sql_code <> 0 then
set sqlMsg = '债券走帐时更新表出错';
set errorMsg = sqlMsg;
goto ERROR_RETURN;
end if;
else
update kjzb.zqinvest set yjtyslx = yjtyslx + ysjlx,ytxyj = ytxyj + txyj,ytxzj = ytxzj + txzj,ytxfy = ytxfy + txfy where id = zid;
if sql_code <> 0 then
set sqlMsg = '债券走帐时更新表出错';
set errorMsg = sqlMsg;
goto ERROR_RETURN;
end if;
end if;
EXIT:
fetch zqtz into vDfJgbm,vzh,hth,zqpz,pmje,pmll,gmrq,dqrq,yslx,yjlx,yj,zj,fy,fxfs,zid,firstday,lastday,firstmonth,lastmonth,firstyear,lastyear;
end while;
close zqtz; -- 关闭游标
COMMIT;
set sqlMsg = '债券投资自动计提成功!';
set errorMsg = sqlMsg;
set sql_code = 0;
set returnCode = sql_code;
return 1;
ERROR_RETURN:
ROLLBACK;
return -1;
DB2 存储过程
一、什么是存储过程
受 DB2 服务器控制的一段可执行程序
可以通过SQL的CALL语句来完成对存储过程的调用
在存储过程中可以包含业务逻辑
存储过程可以在本地或远程进行调用
存储过程可以接收或传递参数,生成结果集
二、存储过程特征
包含使用sql语句的过程构造
存储在数据库中且在db2 服务器上运行;
可以由正在使用的sql的应用程序根据名称来调用;
允许应用程序分2部分允许,在客户机上运行应用程序,在服务器上运行存储过程
存储过程在应用程序中的优势
减少了客户机与服务器直接的网络使用率
增强了硬件和软件功能
提高了安全性
减少了开发成本并且提高了可靠性
集中处理了公共例程的安全性、管理和维护
通过sql pl 当前的语句集合和语言特性,可以用sql开发综合的、高级的程序
例如函数、存储过程和触发器。这样便可以将业务逻辑封装到易于维护的数据库对象中,从而提高数据库应用程序的性能。
SQL PL 支持本地和全局变量,包括声明和赋值,还支持条件语句和迭代语句、控制语句的转移、错误管理语句以及返回结果集的方法。
三、什么时候使用存储过程
使用存储过程的合适时机:
应用程序的性能无法满足预期时
客户端数量较多且应用程序中SQL代码分散时
应用程序需要进行繁重的数据库操作,同时这些操作并不需要进行太多的客户交互
应用程序代码更改频繁
需要对客户应用代码进行访问控制时
客户应用需要在一次操作中执行多条 SQL 语句

五、数据类型

字符型:char varchar
日期型 date
数字型 number decilmal integer
详细请看屌丝大哥 db2数据类型介绍的那一课
六、Db2 存储过程基本语法
6.1 存储过程结构
CREATE OR REPLACE PROCEDURE <过程名>
( [ IN | OUT | INOUT ] 参数名 数据类型 默认值 )
LANGUAGE SQL
BEGIN
业务逻辑代码
END;
IN(输入参数)
只是将实参传递给存储过程,但在存储过程中不能对其进行修改。换句话说,对于存储过程而言它是只读的。
OUT(输出参数)
在存储过程结束时向调用者返回。一般在过程中都会被赋值。
INOUT(输入输出参数)
上述两种参数类型的结合体。它可以帮助调用者将实参传递给进程,另外它也能够作为输出参数被修改和赋值。
复合语句实例
复合语句是指包含在BEGIN和END间的语句。它一般包括如下语句类型:
声明语句
赋值语句
控制语句
条件处理语句


说明:
1. 复合语句可以嵌套使用。
2. BEGIN语句可以和标签组合使用,这样可以更清晰的标识语句块的范围。
6.2 变量声明与变量赋值
变量声明语法:
DECLARE 变量名 数据类型 初始值;
Delcare DiaoSiName varchar(20);
变量赋值语法 :set 变量名=值;
例如:给屌丝姓名变量赋值。
Set DiaoSiName = ‘奶娃’;
变量声明
DECLARE my_var INTEGER DEFAULT 6;
条件声明
DECLARE not_found CONDITION FOR SQLSTATE ‘02000’;
游标声明
DECLARE c1 CURSOR FOR select * from staff;
异常处理器声明
DECLARE EXIT HANDLER FOR SQLEXCEPTION …;
语法
SET lv_name = expression;
SET lv_name = NULL;
示例
(1) SET salary = salary + salary * 0.1;
(2) SET init_salary = NULL;
(3) SET salary = (select salary from employee where empno = lv_emp_num);
注: 如果 SELECT 语句返回记录超过一行,示例 3 将会返回SQLERROR。


模块 - 规格说明(Module Specification)
模块可以发布type, SP, UDF以供外部使用。
CREATE OR REPLACE MODULE myMod;
ALTER MODULE myMod PUBLISH
TYPE myRowTyp AS ANCHOR ROW myTab;
ALTER MODULE myMod PUBLISH
FUNCTION myFunc(val1 ANCHOR myTab.col1)
RETURNS myRowTyp;
ALTER MODULE myMod PUBLISH
PROCEDURE myProc(OUT param1 ANCHOR myTab.col2);
模块 - 实现(Module Implementation)
下面的代码是模块的实现部分:
ALTER MODULE myMod ADD VARIABLE pkgVar ANCHOR myTab.col1;
ALTER MODULE myMod ADD FUNCTION myFunc(val1 ANCHOR myTab.col1) RETURNS myRowTyp
BEGIN
DECLARE var1 myRowTyp;
SELECT * INTO var1 FROM myTab WHERE col1 < val1 AND col1 > pkgVar;
RETURN var1;
END
ALTER MODULE myMod ADD PROCEDURE myProc(OUT param1 ANCHOR myTab.col2)
BEGIN
DECLARE varRow myRowTyp;
SET param1 = varRow.col2 – pkgVar;
END
模块 - 其他语句
删除整个模块
DROP MODULE myMod;
保留规格说明内容,删除实现
ALTER MODULE myMod DROP BODY;
删除模块中的存储过程(SP)
ALTER MODULE myMod DROP PROCEDURE myProc;
将模块的执行权限赋给joe
GRANT EXECUTE ON MODULE myMod TO joe;
格式:
IF 条件1 THEN statement1;
ELSEIF 条件2 THEN statement2;
ELSE statement3;
END IF;
注:条件成立时为TRUE (真),不成立时为FALSE(假) 和 NULL
IF rating = 1 THEN
UPDATE EMPLOYEE SET salary = salary*1.10
WHERE empno = i_num;(如果满足于...时,薪水调整1.1倍)
ELSEIF rating = 2 THEN
UPDATE EMPLOYEE SET salary = salary*1.05
WHERE empno = i_num;
ELSE
UPDATE EMPLOYEE SET salary = salary*1.03
WHERE empno = i_num;
END IF;
CASE语句(1 of 2)
简单CASE语句

稍加变形的CASE语句

LOOP语句
语法
[LABEL] LOOP
SQL-procedure-statements;
END LOOP [LABEL];
示例
fetch_loop: LOOP
FETCH c1 INTO v_firstname, v_lastname;
SET counter = counter + 1;
IF counter = 51 THEN
LEAVE fetch_loop;
END IF;
END LOOP fetch_loop;
语法
[LABEL] FOR for-loop-name AS [cursor-name CURSOR FOR]
select-statement
DO
SQL-procedure-statements;
END FOR [LABEL];
示例
DECLARE fullname CHAR(40);
FOR v1 AS c1 CURSOR FOR SELECT firstnme, midinit, lastname FROM employee
DO
SET fullname=lastname||‘,’||firstnme||’,’||midinit;
INSERT INTO tname VALUE (fullname);
END FOR;
简单地说,观察者模式定义了一个一对多的依赖关系,让一个或多个观察者对象监察一个主题对象。这样一个主题对象在状态上的变化能够通知所有的依赖于此对象的那些观察者对象,使这些观察者对象能够自动更新。
观察者模式的结构 观察者(Observer)模式是对象的行为型模式,又叫做发表-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-收听者(Source/Listener)模式或从属者(Dependents)模式。
本模式的类图结构如下:
 图1、观察者模式的静态结构可从类图中看清楚。 |
在观察者模式里有如下的角色:
. 抽象主题(Subject)角色:主题角色把所有的观察者对象的引用保存在一个列表里;每个主题都可以有任何数量的观察者。主题提供一个接口可以加上或撤销观察者对象;主题角色又叫做抽象被观察者(Observable)角色;
 图2、抽象主题角色,有时又叫做抽象被观察者角色,可以用一个抽象类或者一个接口实现;在具体的情况下也不排除使用具体类实现。 |
. 抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到通知时更新自己;
 图3、抽象观察者角色,可以用一个抽象类或者一个接口实现;在具体的情况下也不排除使用具体类实现。 |
. 具体主题(ConcreteSubject)角色:保存对具体观察者对象有用的内部状态;在这种内部状态改变时给其观察者发出一个通知;具体主题角色又叫作具体被观察者角色;
图4、具体主题角色,通常用一个具体子类实现。
.具体观察者(ConcreteObserver)角色:保存一个指向具体主题对象的引用;和一个与主题的状态相符的状态。具体观察者角色实现抽象观察者角色所要求的更新自己的接口,以便使本身的状态与主题的状态自恰。
 图5、具体观察者角色,通常用一个具体子类实现。 |
下面给出一个示意性实现的Java代码。首先在这个示意性的实现里,用一个Java接口实现抽象主题角色,这就是下面的Subject接口:
public interface Subject { public void attach(Observer observer);
public void detach(Observer observer);
void notifyObservers(); }
|
代码清单1、Subject接口的源代码。
这个抽象主题接口规定出三个子类必须实现的操作,即 attach() 用来增加一个观察者对象;detach() 用来删除一个观察者对象;和notifyObservers() 用来通知各个观察者刷新它们自己。抽象主题角色实际上要求子类保持一个以所有的观察者对象为元素的列表。
具体主题则是实现了抽象主题Subject接口的一个具体类,它给出了以上的三个操作的具体实现。从下面的源代码可以看出,这里给出的Java实现使用了一个Java向量来保存所有的观察者对象,而 attach() 和 detach() 操作则是对此向量的元素增减操作。
import java.util.Vector; import java.util.Enumeration;
public class ConcreteSubject implements Subject { public void attach(Observer observer) { observersVector.addElement(observer); }
public void detach(Observer observer) { observersVector.removeElement(observer); }
public void notifyObservers() { Enumeration enumeration = observers(); while (enumeration.hasMoreElements()) { ((Observer)enumeration.nextElement()).update(); } }
public Enumeration observers() { return ((Vector) observersVector.clone()).elements(); } private Vector observersVector = new java.util.Vector(); }
|
代码清单2、ConcreteSubject类的源代码。
抽象观察者角色的实现实际上是最为简单的一个,它是一个Java接口,只声明了一个方法,即update()。这个方法被子类实现后,一被调用便刷新自己。
public interface Observer { void update(); } |
代码清单3、Observer接口的源代码。
具体观察者角色的实现其实只涉及update()方法的实现。这个方法怎么实现与应用密切相关,因此本类只给出一个框架。
public class ConcreteObserver implements Observer { public void update() { // Write your code here } } |
代码清单4、ConcreteObserver类的源代码。
虽然观察者模式的实现方法可以有设计师自己确定,但是因为从AWT1.1开始视窗系统的事件模型采用观察者模式,因此观察者模式在Java语言里的地位较为重要。正因为这个原因,Java语言给出了它自己对观察者模式的支持。因此,本文建议读者在自己的系统中应用观察者模式时,不妨利用Java语言所提供的支持。
Java语言提供的对观察者模式的支持 在Java语言的java.util库里面,提供了一个Observable类以及一个Observer接口,构成Java语言对观察者模式的支持。
Observer接口 这个接口只定义了一个方法,update()。当被观察者对象的状态发生变化时,这个方法就会被调用。这个方法的实现应当调用每一个被观察者对象的notifyObservers()方法,从而通知所有的观察对象。
 图6、java.util提供的Observer接口的类图。 |
package java.util;
public interface Observer { /** * 当被观察的对象发生变化时,这个方法会被调用。 */ void update(Observable o, Object arg); }
|
代码清单5、java.util.Observer接口的源代码。
Observable类 被观察者类都是java.util.Observable类的子类。java.util.Observable提供公开的方法支持观察者对象,这些方法中有两个对Observable的子类非常重要:一个是setChanged(),另一个是notifyObservers()。第一个方法setChanged()被调用之后会设置一个内部标记变量,代表被观察者对象的状态发生了变化。第二个是notifyObservers(),这个方法被调用时,会调用所有登记过的观察者对象的update()方法,使这些观察者对象可以更新自己。
java.util.Observable类还有其它的一些重要的方法。比如,观察者对象可以调用java.util.Observable类的addObserver()方法,将对象一个一个加入到一个列表上。当有变化时,这个列表可以告诉notifyObservers()方法那些观察者对象需要通知。由于这个列表是私有的,因此java.util.Observable的子对象并不知道观察者对象一直在观察着它们。
 图7、Java语言提供的被观察者的类图。 |
被观察者类Observable的源代码:
package java.util;
public class Observable
{
private boolean changed = false;
private Vector obs;

/** 用0个观察者构造一个被观察者。**/

public Observable()
{
obs = new Vector();
}

/**
* 将一个观察者加到观察者列表上面。
*/
public synchronized void addObserver(Observer o)
{
if (!obs.contains(o))
{
obs.addElement(o);
}
}

/**
* 将一个观察者对象从观察者列表上删除。
*/
public synchronized void deleteObserver(Observer o)
{
obs.removeElement(o);
}

/**
* 相当于 notifyObservers(null)
*/
public void notifyObservers()
{
notifyObservers(null);
}

/**
* 如果本对象有变化(那时hasChanged 方法会返回true)
* 调用本方法通知所有登记在案的观察者,即调用它们的update()方法,
* 传入this和arg作为参量。
*/
public void notifyObservers(Object arg)
{
/**
* 临时存放当前的观察者的状态。参见备忘录模式。
*/
Object[] arrLocal;

synchronized (this)
{
if (!changed) return;
arrLocal = obs.toArray();
clearChanged();
}

for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}

/**
* 将观察者列表清空
*/
public synchronized void deleteObservers()
{
obs.removeAllElements();
}

/**
* 将“已变化”设为true
*/
protected synchronized void setChanged()
{
changed = true;
}

/**
* 将“已变化”重置为false
*/
protected synchronized void clearChanged()
{
changed = false;
}

/**
* 探测本对象是否已变化
*/
public synchronized boolean hasChanged()
{
return changed;
}

/**
* 返还被观察对象(即此对象)的观察者总数。
*/
public synchronized int countObservers()
{
return obs.size();
}
}

代码清单6、java.util.Observer接口的源代码。
这个Observable类代表一个被观察者对象。一个被观察者对象可以有数个观察者对象,一个观察者可以是一个实现Observer接口的对象。在被观察者对象发生变化时,它会调用Observable的notifyObservers方法,此方法调用所有的具体观察者的update()方法,从而使所有的观察者都被通知更新自己。见下面的类图:
 图8、使用Java语言提供的对观察者模式的支持。 |
发通知的次序在这里没有指明。Observerable类所提供的缺省实现会按照Observers对象被登记的次序通知它们,但是Observerable类的子类可以改掉这一次序。子类并可以在单独的线程里通知观察者对象;或者在一个公用的线程里按照次序执行。
当一个可观察者对象刚刚创立时,它的观察者集合是空的。两个观察者对象在它们的equals()方法返回true时,被认为是两个相等的对象。
怎样使用Java对观察者模式的支持 为了说明怎样使用Java所提供的对观察者模式的支持,本节给出一个非常简单的例子。在这个例子里,被观察对象叫做Watched,也就是被监视者;而观察者对象叫做Watcher。Watched对象继承自java.util.Obsevable类;而Watcher对象实现了java.util.Observer接口。另外有一个对象Tester,扮演客户端的角色。
这个简单的系统的结构如下图所示。
 图9、一个使用Observer接口和Observable类的例子。 |
在客户端改变Watched对象的内部状态时,Watched就会通知Watcher采取必要的行动。
代码清单7、Tester类的源代码。
代码清单7、Tester类的源代码。
代码清单9、Watcher类的源代码。
可以看出,虽然客户端将Watched对象的内部状态赋值了四次,但是值的改变只有三次:
 watched.changeData("In C, we create bugs.");  watched.changeData("In Java, we inherit bugs.");  watched.changeData("In Java, we inherit bugs.");  watched.changeData("In Visual Basic, we visualize bugs."); |
代码清单10、被观察者的内部状态发生了改变。
对应地,Watcher对象汇报了三次改变,下面就是运行时间程序打印出的信息:
代码清单11、运行的结果。
菩萨的守瓶龟 想当年齐天大圣为解救师傅唐僧,前往南海普陀山请菩萨降伏妖怪红孩儿:“菩萨听说...恨了一声,将手中宝珠净瓶往海心里扑的一掼...只见那海当中,翻波跳浪,钻出个瓶来,原来是一个怪物驮着出来...要知此怪名和姓,兴风作浪恶乌龟。”
使用面向对象的语言描述,乌龟便是一个观察者对象,它观察的主题是菩萨。一旦菩萨将净瓶掼到海里,就象征着菩萨作为主题调用了notifyObservers()方法。在西游记中,观察者对象有两个,一个是乌龟,另一个是悟空。悟空的反应在这里暂时不考虑,而乌龟的反应便是将瓶子驮回海岸。
 图10、菩萨和菩萨的守瓶乌龟。 |
Java中的DEM事件机制 AWT中的DEM机制 责任链模式一章中曾谈到,AWT1.0的事件处理的模型是基于责任链的。这种模型不适用于复杂的系统,因此在AWT1.1版本及以后的各个版本中,事件处理模型均为基于观察者模式的委派事件模型(Delegation Event Model或DEM)。
在DEM模型里面,主题(Subject)角色负责发布(publish)事件,而观察者角色向特定的主题订阅(subscribe)它所感兴趣的事件。当一个具体主题产生一个事件时,它就会通知所有感兴趣的订阅者。
使用这种发布-订阅机制的基本设计目标,是提供一种将发布者与订阅者松散地耦合在一起的联系形式,以及一种能够动态地登记、取消向一个发布者的订阅请求的办法。显然,实现这一构思的技巧,是设计抽象接口,并把抽象层和具体层分开。这在观察者模式里可以清楚地看到。
使用DEM的用词,发布者叫做事件源(event source),而订阅者叫做事件聆听者(event listener)。在Java里面,事件由类代表,事件的发布是通过同步地调用成员方法做到的。
Servlet技术中的的DEM机制 AWT中所使用的DEM事件模型实际上被应用到了所有的Java事件机制上。Servlet技术中的事件处理机制同样也是使用的DEM模型。
SAX2技术中的DEM机制 DEM事件模型也被应用到了SAX2的事件处理机制上。
观察者模式的效果 观察者模式的效果有以下的
优点:
第一、观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体观察者列表,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。
由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。如果被观察者和观察者都被扔到一起,那么这个对象必然跨越抽象化和具体化层次。
第二、观察者模式支持广播通讯。被观察者会向所有的登记过的观察者发出通知,
观察者模式有下面的
缺点:
第一、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
第二、如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式是要特别注意这一点。
第三、如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。
第四、虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。
观察者模式与其它模式的关系 观察者模式使用了备忘录模式(Memento Pattern)暂时将观察者对象存储在被观察者对象里面。
问答题 第一题、我和妹妹跟妈妈说:“妈妈,我和妹妹在院子里玩;饭做好了叫我们一声。”请问这是什么模式?能否给出类图说明?
问答题答案 第一题答案、这是观察者模式。我和妹妹让妈妈告诉我们饭做好了,这样我们就可以来吃饭了。换用较为技术化的语言来说,当系统的主题(饭)发生变化时,就告诉系统的其它部份(观察者们,也就是妈妈、我和妹妹),使其可以调整内部状态(有开始吃饭的准备),并采取相应的行动(吃饭)。
系统的类图说明如下。
 图11、系统的类图。 |
网上商店中的商品在名称、价格发生变化时,必须自动通知会员,Java的API为我们提供了Observer接口和Observable类来实现所谓观察者模式。
Observable(可观察者)类允许在自身发生改变时,通知其它对象(实现接口Observer,观察者)。
下面是一个可观察者(产品类):
import java.util.*;
public class product extends Observable{
private String name;////产品名
private float price;////价格
public String getName(){ return name;}
public void setName(String name){
this.name=name;
////设置变化点
setChanged();
notifyObservers(name);////通知观察者
}
public float getPrice(){ return price;}
public void setPrice(float price){
this.price=price;
////设置变化点
setChanged();
notifyObservers(new Float(price));
}
////以下可以是数据库更新 插入命令.
public void saveToDb(){
System.out.println("saveToDb");
}
}
下面是两个观察者:
import java.util.*;
public class NameObserver implements Observer{
private String name=null;
public void update(Observable obj,Object arg){
if (arg instanceof String){
name=(String)arg;
////产品名称改变值在name中
System.out.println("NameObserver :name changet to "+name);
}
}
}
import java.util.*;
public class PriceObserver implements Observer{
private float price=0;
public void update(Observable obj,Object arg){
if (arg instanceof Float){
price=((Float)arg).floatValue();
System.out.println("PriceObserver :price changet to "+price);
}
}
}
下面是测试类:
public class Test {
public static void main(String args[]){
Product product=new Product();
NameObserver nameobs=new NameObserver();
PriceObserver priceobs=new PriceObserver();
////加入观察者
product.addObserver(nameobs);
product.addObserver(priceobs);
product.setName("applet");
product.setPrice(9.22f);
}
}
自从图形化界面成为个人桌面电脑的主流,应用程序复杂程度与日俱增,针对人机交互的自动化测试迫在眉睫,从而在市场上涌现了一大批针对图形界面应用程序功能测试的自动化测试工具(参考链接1)。2001年QTP第一个版本发布;2002年Robot初始版发布。自此,自动化工具已经经历了十年的发展。随着近两年移动应用呈现爆炸性的增长,移动应用自动化测试工具也开始陆续呈现(参考链接2)。
需求的延续
无论从PC端应用的自动化测试,还是移动应用的自动化测试,人们的关注点从未转移,期望也从不改变,那就是,尽可能多的模拟人工测试动作和相应的结果检查,从而释放手工劳动,替代大量重复性的执行和验证工作。进入移动应用时代,移动应用项目开发一直走的是“短小精干”的路子,即应用程序小而精。开发模式也抛弃了传统的规范流程,热衷于敏捷式开发。版本发布周期约来越短,迭代频密。这些似乎与自动化测试遥不可及。但是,随着移动应用逐渐从个人娱乐领域渗透到商业应用,诸如金融、办公、政务等方面的应用比重逐步扩大,对移动应用质量的要求也越来越高,自动化测试始终会回到人们的视线之内。在加上安卓特有的碎片化问题,使得安卓平台自动化回归测试和兼容性测试的呼声极高。
理念的传承
回顾桌面应用的自动化测试历程,我们看到,工具的发展经历了从最初“坐标点操作”过渡到“对象识别”的过程。移动应用测试工具走的路子也有几分相似。以开放的Android平台为例,最开始出现Monkey/MonkeyRunner等坐标点操作的工具(后来有很多工具开发商做了对MonkeyRunner的封装);之后出现了如Robotium等基于源码层面对于界面控件识别的工具;也有一些工具开发商如DroidPilot.com推出了纯粹的对象识别工具;当然,也有一些如PerfectoMobile.com的工具开发商,为了兼容iOS/BlackBerry/Windows Phone等平台,采用图像识别技术。但无论如何,“关键字驱动”、“数据驱动”等理念已经是传统PC行业自动化测试的成功经验,移动应用测试方面应该借鉴。再搭配性能测试工具、轻量级测试需求管理、用例管理、缺陷跟踪等工具,相信足以成为移动应用项目质量保证的基础工具支撑。
有所不能vs凡事都能
似乎所有管理者都期望一旦引入自动化测试,则万事大吉,貌似自动化能做到全方位的测试服务,可以释放测试工程师了。但事实求是的说,即使在拥有十年历程的传统自动化测试行业,自动化所能涉及的测试用例比例也是有限,通常覆盖60%~80%的测试用例,已经能说是不错的成绩了。问题是,项目的成本和进度,以及测试人员的配备,是否能足以支撑自动化测试持续的进行。否则事半功倍,未免太可惜了。借鉴传统项目的自动化测试失败案例,对于项目预算相对较少的移动应用开发项目,考虑引入自动化测试的确需要慎之又慎。
精益求精
然而对于自动化测试工程师来说,通常并不满足于部分用例的自动化测试,甚至仅仅是自动化冒烟测试。他们总想走的更远,甚至不惜代价去完善一些凤毛麟角之功能。当然,从这一点也可以看出自动化测试工程师们精益求精的精神,同时,也对自动化测试工具开发者提出了更高的要求。从目前发展现状来看,他们也的确在着眼于提高工具的测试深度和广度,增强工具易用性,剥离工具对于源代码的依赖,延伸传统自动化测试的方法论。希望看到移动应用自动化测试领域呈现蓬勃的发展。
参考链接
1、<List of GUI testing tools - wikipedia>
http://en.wikipedia.org/wiki/List_of_GUI_testing_tools
2、<安卓应用自动化测试工具大汇总–测试窝>
http://www.testwo.com/space.php?uid=11328&do=blog&id=5956
版权声明:本文出自 anthony.wang 的51Testing软件测试博客:http://www.51testing.com/?507238
原创作品,转载时请务必以超链接形式标明本文原始出处、作者信息和本声明,否则将追究法律责任。
又属于一篇普及文,希望自己在被各种技术吸引的同时,能时常来整理和总结软件测试最基本的知识。
从刚工作时接触的第一个缺陷管理工具禅道,到redmine、JIRA、bugzilla ,再到现在的QC,当然还有其它种的开源的或商业的缺陷管理工具,它们的本质是一样的,就是来管理缺陷的生命周期。
其实,你理解任意的一款工具,其它的工具也一定能无师自通。这不谈某款工具,单把它本质的一些东西抽离出来与大家分享。
Bug的属性
Bug重现环境
这个应该是我们重现bug的一个前提,如果没有这个前提,我们可能会无法重现问题,或者跟本就无从下手。
操作系统
这个是一般软件运行的一大前提,基本上所有的软件都依赖于操作系统之上的,对于一个软件来说,要想在某个操作系统上运行,必须要对这个操作系统支持,这就需要有真对性的设计与开发。对于不同的操作系统,其可能存在差异(如:win xp 与 win 7)或本质的区别(如 win 7 与 CentOS linux ),所以,操作系统环境是重现问题的一个重要前提。
浏览器
对于B/S系统,或面向大众的互联网产品(网站,邮箱等),浏览器的兼容性也是必须测试的一个重点,对于现在的浏览器市场,各式的浏览器都有其用户群,要想使产品大众化,必须考虑这些产品的兼容性问题。
不同的浏览器之间(IE、 firefox、chrome、opera 等),甚至同一系列不同版本(ie6/ie7/ie8/ie9等)都可能存在兼容性问题,所以,对于这类应用,浏览器环境重现bug前提条件之一。
其它(这个“其它”非常重要)
对于不同的系统发现重现问题,都会有其特定的前提,拿我测试的邮箱来说,必须要描述其是在测试线还是现网环境,而且还要附带一重现问题的帐号等。
对于c/s软件,可能还要考虑与其它常用软的兼容等,例如,是在安装的某款软件后,对本软件的安装和使用造成影响。这些都是重现问题的必须描述的环境。
问题类型
根据JIRA的管理系统的划分,bug 只是问题的一种,它可以用于跟踪多种不同类型的问题(其实,他只是将bug做为一子类而已)。
JIRA系统缺省提供的问题类型(大部分的系统都可以自定义类型的,这样就增加了灵活性。)
● Bug : 测试过程、维护过程发现影响系统运行的缺陷。(这就是一般测试人员所提交的bug)
● New Feature : 对系统提出的新功能。(单个的小需求可以,如果大的话,就相当于一个需求,放到这里是不合理的。)
● Task : 需要完成的一任务。(开发或测试任务指派。)
● Improvement : 对现有系统功能的改进。(一般产品经理或产品体验师做的事)
当然,不同的公司,他们的人员定位与职责是不太相同的,按照上面的分类,JIRA就不是简单的缺陷管理系统了,它涵盖一项目(或产品)所需要处理的任务、需求与缺陷。
Bug 类型:
这里缩小范围,单指我们测试人员在测试过程中发现的缺陷,发现产品缺陷其实就是测试人员工作的主要目的。当然,你要确定一个问题的类型,也需要对项目(或产品)有比较深的理解。是代码缺陷还是设计缺陷有时候就不太容易区分,当然,这个划分,对于开发定位问题影响很小,但对于问题类型的统计就比较重要了。
下面看一些常见的分类:
划分方式一:
代码错误
设计缺陷
界面优化
配置相关
安装部署
性能问题
标准规范
测试代码
其它
划分方式二:
功能类(function)
性能类(performance)
界面类(UI)
易用性类(usability)
兼容性类(compatibility)
其它(else)
这个分类当然是可以自定义的,具我接触的缺陷管理都是可以自定义的,既然是对问题的管理,那么你当然可以拿来做特定环境下的系统来使用,或我就想用这个系统来指派任务,那么我的自定义类型为前端任务、后端任务、测试任务、配置部署...
缺陷等级
缺陷等级,这个划分也比较灵活,有分三级或四级,也有分五级的。
致命
一招毙命的缺陷,使你的系统无法运行,有造成数据泄漏的安全性问题。
严重
可以引起易于纠正的异常情况、可能引起易于修复的故障或对产品外观难以接受的缺陷。
一般
指不影响产品的运转和运行、不会成为故障起因,但对产品外观和下道工序影响较大的缺陷
轻微
轻微缺陷是指对产品外观和下道工序可能会有轻微影响的缺陷
建议
增加用户使用体验的建议性问题。(一般情况下,建议也为做为缺陷的一种。这个跟系统的类型与需求有关)
缺陷优先级(priority)
当问题处理人员在面对许多问题需要处理进,就需要问题进行优先级排序。我们做事情的安排,操作系统有处理进程等都在使用着优先级。
优先级的划分:
低——>中——>高——>紧急
延迟处理——>正常排队——>优先处理——>紧急处理
Bug 的严重程度和优先级是含义不同但相互联系密切的两个概念,它们从不同的侧面描述了软件缺陷对软件质量和最终用户的影响程序和处理方式。
一般地,严重程序高的软件缺陷具有较高的优先级。严重程度高说明缺陷对软件造成的危害性大,需要优先处理,而来严重程序低的缺陷可能只是软件不太尽善尽美,可以稍后处理。
严重程度高优先级不一定高:
如果某个严重的软件缺陷只在非常极端的条件下产生,则没有必要马上处理。
如果某一个软件缺陷,需要重新修改软件的整体架构,可能会产生更多的潜在缺陷,而且软件由于市场的压力必须尽快发布,此时即使缺陷的严重性很高,是否需要修正,需要全盘考虑。
严重程度优先级不一定低
如果是软件名称或公司名称的拼写错误,虽然说其属于界面错误,严重程度不高,但其关系到软件和公司的市场开解,必须尽快修正。
缺陷状态
对于一个问题,其处理过程是一个周期,周期的不同阶段,其所处的状态也是不一样的。不同状态所对应的处理人也是不一样的。
打开:表示问题被提交等待有人处理。
重新指派:问题被重新指派给某人处理。
处理:问题在处理中,尚未完成。
固定:确认此问题存在,但暂时不进行处理。
回归:对已经修复的问题进行回归确认。Reopened :
关闭:问题的最后一个状态。
Bug处理流程
下面通过一个比较完整的bug的处理流程图,更深刻的理解bug的状态以一个bug的生命周期。

提交(打开)缺陷
在提交一个缺陷的缺陷,首先尽量描述这个缺陷的属性。Bug重现环境,bug类型,bug等级,bug的优先级以及详细的重现步骤,结果与期望等。
当然,我们在提交一个问题之前首先应该保证,这个缺陷是没有被提过的,以免造成重复缺陷单。
如果是回归不通过的缺陷,其状态又会变为打开状态。
分配(转交)缺陷
这一步不是必须的,跟项目模式有关,有些公司测试部门与开发部门独立,那么测试人员就不确定自己测试的模块是由哪位开发人员负责的,在这种情况下,测试人员统一把问题指派给项目组长或经理,由项目组长(或经理)对问题进行确认后再次分配给相应的开发人员。
有些测试人员是穿插到不同研发团队中的,所以对不同的开人发员负责的开发模块非常清楚,这个时候就可以将问题直接指派给相应的开发人员。
也有一种情况,本来此问题应该由A开发人员负责,但由于A开发人员的调离或辞职,些问题为转交给其它人员处理。“分配”强调是上级对下级;“转交”强调的是平级之间。
确认缺陷
当开发人员接到一个缺陷时,首先是对其进行分析与重现,如果对其进行分析发现不是缺陷(可能由于测试人员不了解需求)或无法对此问题进行重现,那么就需要将此问题反回给测试人员,并注明原因。如果确认为缺陷则需要对其进行处理。
推迟处理
在处理问题之后,还需要进行一次判断,是否需要推迟处理,有些需求已经确认了是问题,由于其可能在极端情况下才会出现,或需要对系统架构进行改动,或其优先级非常低,所以暂时不需要对此问题进行处理(或到下个版本进再进行修复)。
固定
对于推迟处理的问题可以暂时进行固定(“固定”为QC中的叫法。)一般固定的问题需要经过项目经理与测试经理协商后才能固定。
处理缺陷
开发人员在确认完一个问题需要处理时,那么就对其进行处理工作。(例如,redmine 是支持处理人时时更新问题处理进度的,如 已处理30% ,已处理80% 等,当然,对于短时间内可以修复的问题就没必要时时的去更新处理进度。)
回归缺陷
回归缺陷对于测试人员来说是非常重要的工作,其有三个入口两个出口。
确认非缺陷问题:对于提交的一个缺陷,开人员处理为非问题或无法重现,然后直接转交给测试人员回归。测试人员再次确认,如果真如开发人员所说,则将问题关闭。如果非开发人员所说,是由于问题描述模糊或其它原因喂重现问题,则再次注明原因转给开发人员。
确认修复问题:对开发人员修复的问题再次进行确认,确认能过,则关闭问题。确认不通过,将问题再次打开并转给开发人员。
确认固定问题:有计划的对固定问题进行确认,有些固定问题随着时间的推移,版本的更新或已经不存在了,对这类问题应该及时关闭。有些固定问题依然存在且变得紧急,对于这类问题应该及时打开交给开发人员处理。
关闭缺陷
对于已经修复的缺陷进行关闭,这也是一个缺陷的最后一个状态。
--------------------------------------------------------------------------------
注1:文中提到了产品与项目,好多人分不清项目与产品,各自有各自的理解。我个人从用户群上来划分。如果面向的是特定客户的需求,那么称其为项目,如某医院的医疗系统,某公司的管理系统。面向大众用户且长期运营的需求,称为产品,如,某网站,某网络游戏。
如果小A让我给他做一个网站呢?对于我来说,小A是我的客户,这个网站对我来说就是一个项目,对于小A来说,他的网站是面向大众用户的,那么对于小A来说,网站就是自己的产品。
富士康带工苹果手机是一样的道理,富士康接到苹果的订单,那么对富士康来说是个项目,完成项目,拿到钱就算项目结束。苹果手机对苹果公司来说是一个产品,它长期持有这个产品的所有权,并且不段的更新自己的产品。
注2:本文中用到了 bug、缺陷、问题等三个词语,用词比较模糊,本文中表示为一个事物。
分配(转交)缺陷
这一步不是必须的,跟项目模式有关,有些公司测试部门与开发部门独立,那么测试人员就不确定自己测试的模块是由哪位开发人员负责的,在这种情况下,测试人员统一把问题指派给项目组长或经理,由项目组长(或经理)对问题进行确认后再次分配给相应的开发人员。
有些测试人员是穿插到不同研发团队中的,所以对不同的开人发员负责的开发模块非常清楚,这个时候就可以将问题直接指派给相应的开发人员。
也有一种情况,本来此问题应该由A开发人员负责,但由于A开发人员的调离或辞职,些问题为转交给其它人员处理。“分配”强调是上级对下级;“转交”强调的是平级之间。
确认缺陷
当开发人员接到一个缺陷时,首先是对其进行分析与重现,如果对其进行分析发现不是缺陷(可能由于测试人员不了解需求)或无法对此问题进行重现,那么就需要将此问题反回给测试人员,并注明原因。如果确认为缺陷则需要对其进行处理。
推迟处理
在处理问题之后,还需要进行一次判断,是否需要推迟处理,有些需求已经确认了是问题,由于其可能在极端情况下才会出现,或需要对系统架构进行改动,或其优先级非常低,所以暂时不需要对此问题进行处理(或到下个版本进再进行修复)。
固定
对于推迟处理的问题可以暂时进行固定(“固定”为QC中的叫法。)一般固定的问题需要经过项目经理与测试经理协商后才能固定。
处理缺陷
开发人员在确认完一个问题需要处理时,那么就对其进行处理工作。(例如,redmine 是支持处理人时时更新问题处理进度的,如 已处理30% ,已处理80% 等,当然,对于短时间内可以修复的问题就没必要时时的去更新处理进度。)
回归缺陷
回归缺陷对于测试人员来说是非常重要的工作,其有三个入口两个出口。
确认非缺陷问题:对于提交的一个缺陷,开人员处理为非问题或无法重现,然后直接转交给测试人员回归。测试人员再次确认,如果真如开发人员所说,则将问题关闭。如果非开发人员所说,是由于问题描述模糊或其它原因喂重现问题,则再次注明原因转给开发人员。
确认修复问题:对开发人员修复的问题再次进行确认,确认能过,则关闭问题。确认不通过,将问题再次打开并转给开发人员。
确认固定问题:有计划的对固定问题进行确认,有些固定问题随着时间的推移,版本的更新或已经不存在了,对这类问题应该及时关闭。有些固定问题依然存在且变得紧急,对于这类问题应该及时打开交给开发人员处理。
关闭缺陷
对于已经修复的缺陷进行关闭,这也是一个缺陷的最后一个状态。
--------------------------------------------------------------------------------
注1:文中提到了产品与项目,好多人分不清项目与产品,各自有各自的理解。我个人从用户群上来划分。如果面向的是特定客户的需求,那么称其为项目,如某医院的医疗系统,某公司的管理系统。面向大众用户且长期运营的需求,称为产品,如,某网站,某网络游戏。
如果小A让我给他做一个网站呢?对于我来说,小A是我的客户,这个网站对我来说就是一个项目,对于小A来说,他的网站是面向大众用户的,那么对于小A来说,网站就是自己的产品。
富士康带工苹果手机是一样的道理,富士康接到苹果的订单,那么对富士康来说是个项目,完成项目,拿到钱就算项目结束。苹果手机对苹果公司来说是一个产品,它长期持有这个产品的所有权,并且不段的更新自己的产品。
注2:本文中用到了 bug、缺陷、问题等三个词语,用词比较模糊,本文中表示为一个事物。