用户访问某个网站或软件,一般不会不停地做个各种操作,例如一次查询,用户需要时间查看查询的结果是否是自己想要的。例如一次订单提交,用户需要时间核对自己填写的信息是否正确等。
也就是说用户在做某些操作时,是会有停留时间的,我把这个时间叫思考时间。但利用代码去执行的时候是没有时间的,当然,脚本运行本身是需要时间的,但比起人的思考时间要小很多。这也是我们为什么要用软件来代替人的某些工作。
但有时候,我们在做性能测试时,为了更真实的模拟用户的操作,需要给代码加入思考时间。来看看在loadrunner是如何设置思考时间的。
打开loadrunner 的Virtual User Generator
菜单栏:Vuser ---Run-Time settings...,切换到Thark Time选项
好吧!为了更好的理解这个界面上设置,现在我们通过一个脚本来分析。
Action() { double trans_time; //定义变量 web_url("www.baidu.com", "URL=http://www.baidu.com/", "Resource=0", "RecContentType=text/html", "Referer=", "Snapshot=t1.inf", "Mode=HTML", LAST); web_url("i-1.0.0.png", "URL=http://s1.bdstatic.com/r/www/img/i-1.0.0.png", "Resource=1", "RecContentType=image/png", "Referer=http://www.baidu.com/", "Snapshot=t2.inf", LAST);
web_url("favicon.ico", "URL=http://www.baidu.com/favicon.ico", "Resource=1", "RecContentType=image/x-icon", "Referer=", "Snapshot=t3.inf", LAST); web_url("su", "URL=http://suggestion.baidu.com/su?wd=&cb=window.bdsug.sugPreRequest&sid=1466&t=1362316450913", "Resource=1", "RecContentType=baiduapp/json", "Referer=http://www.baidu.com/", "Snapshot=t4.inf", LAST); lr_start_transaction("注册"); //添加事务 lr_think_time(20); //设置思考时间
web_url("v.gif", "URL=http://nsclick.baidu.com/v.gif?pid=201&pj=www&rsv_sid=1466&fm=behs&tab=tj_reg&un=&path=http%3A%2F%2Fwww.baidu.com%2F&t=1362316485456", "Resource=1", "Referer=http://www.baidu.com/", LAST); web_url("favicon.ico_2", "URL=https://passport.baidu.com/favicon.ico", "Resource=1", "RecContentType=application/octet-stream", "Referer=", "Snapshot=t5.inf", LAST);
web_link("注册", "Text=注册", "Snapshot=t6.inf", LAST); web_custom_request("urs.asmx", "URL=https://urs.microsoft.com/urs.asmx?MSURS-Client-Key=WI0pAZHPdb%2b3UDOD0AtzxA%3d%3d&MSURS-Patented-Lock=S1IpDfNCCC4%3d", "Method=POST", "Resource=0", "RecContentType=text/xml", "Referer=", "Snapshot=t7.inf", "Mode=HTML", "EncType=text/xml; charset=utf-8", "Body=<RepLookup v=\"3\"><G>ED8654D5-B9F0-4DD9-B3E8-F8F560086FDF</G><O>F03F2D77-79E1-4DEC-BBF8-81A5C0790160</O><D>9.0.8110.0</D><C>9.00.8112.16421</C><OS>6.1.7601.1.0</OS><I>9.0.8112.16421</I><L>zh-CN</L><R><Rq><URL>aHR0cHM6Ly9wYXNzcG9ydC5iYWlkdS5jb20vdjIvP3JlZyZyZWdUeXBlPTEmdHBsPW1uJnU9aHR0cCUzQSUyRiUyRnd3dy5iYWlkdS5jb20lMkY=</URL><O>PRE</O><T>TOP</T><HIP>220.181.111.48</HIP></Rq></R></RepLookup>", EXTRARES, "Url=https://passport.baidu.com/img/topbarnav_bg.png", "Referer=https://passport.baidu.com/v2/?reg®Type=1&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F", ENDITEM, "Url=https://passport.baidu.com/passApi/js/reg_6e270622.js", "Referer=https://passport.baidu.com/v2/?reg®Type=1&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F", ENDITEM, "Url=https://passport.baidu.com/passApi/img/small_blank_9dbbfbb1.gif", "Referer=https://passport.baidu.com/v2/?reg®Type=1&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F", ENDITEM, LAST); web_url("api", "URL=https://passport.baidu.com/v2/api/?getapi&tpl=mn&apiver=v3&tt=1362316491971&class=reg&callback=bd__cbs__c93h6w", "Resource=0", "RecContentType=text/html", "Referer=https://passport.baidu.com/v2/?reg®Type=1&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F", "Snapshot=t8.inf", "Mode=HTML", EXTRARES, "Url=/img/v.gif?type=1®Type=mail", "Referer=https://passport.baidu.com/v2/?reg®Type=1&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F", ENDITEM, LAST); web_url("api_2", "URL=https://passport.baidu.com/v2/api/?getapi&tpl=mn&apiver=v3&tt=1362316491978&class=regPhone&callback=bd__cbs__xs2rv5", "Resource=0", "RecContentType=text/html", "Referer=https://passport.baidu.com/v2/?reg®Type=1&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F", "Snapshot=t9.inf", "Mode=HTML", EXTRARES, "Url=/cgi-bin/genimage?001362316472015690A3686612594D3D2C14ABF30D0B432CF4462DF3A4B5A8E5E7B7EE094DCAB428311C96E23B35A3CB73A67533699084E25A27CF6C9745497B5C5435611E0593A615CC284B27D70CA5FFB5F771E5D7DC9EE266F6FD61A2D88FEC7B7BA20EFF5DE2CD301E011E634D5063BDDCD35A47C1EB7B5EF365B347DD5800C612B744FAEF845540239F1036CC800902957BD17F36F6547B71C198C154162F8F71077B935E6B", "Referer=https://passport.baidu.com/v2/?reg®Type=1&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F", ENDITEM, "Url=/img/bd-split.gif", "Referer=https://passport.baidu.com/v2/?reg®Type=1&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F", ENDITEM, "Url=/img/reg_tip_header_t.gif", "Referer=https://passport.baidu.com/v2/?reg®Type=1&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F", ENDITEM, "Url=/img/reg_tip_bg_bottom.gif", "Referer=https://passport.baidu.com/v2/?reg®Type=1&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F", ENDITEM, LAST); trans_time=lr_get_transaction_wasted_time("注册"); //获得消耗时间
if (trans_time) { lr_output_message("The duration up to the submit is %f seconds",trans_time); //打印输出消耗实时间 }else{ lr_output_message("the duration cannot be determined. "); } lr_end_transaction("注册",LR_AUTO); //事务结束 return 0; } |
脚本有点长,但不要被吓到了,重要的几句我已经加了注释。你只用关注那几行就行了。
lgnore think time:忽视思考时间,也就说勾选这一项的时候 ,你脚本中加入的lr_think_time 函数设置是无效的。选中这一项,运行上面的脚本。
Action.c(62): The duration up to the submit is 0.032043 seconds Action.c(67): Notify: Transaction "注册" ended with "Pass" status (Duration: 12.4113 Wasted Time: 0.0320). |
虽然我在脚本中加入了lr_think_time(20); 这条语句,显现没起到任何作用。
Replay think time:回放思考时间
来看第一个选项:
As recorded:取决于记录,选种这一项再来运行上面的脚本。
Action.c(111): The duration up to the submit is 0.034508 seconds Action.c(116): Notify: Transaction "注册" ended with "Pass" status (Duration: 50.4453 Think Time: 20.0000 Wasted Time: 0.0345). Ending action Action. |
运行结果中多了一个 Think Time: 20.0000;这说明我们脚本中的lr_think_time(20); 起到了作用,选中这一项后,我们可以在脚本中任意的添加思考时间。
Multiply recorded think time by:录制思考时间乘以,使用录制思考时间的倍数。
其实,在我们脚本录制的过程中,根据我们的操作,脚本中就可以生成相应的思考时间。这个参数是根据脚本中的思考时间进行调整的。
例如:脚本是20秒,输入2,那么在脚本实际运行时,思考时间将为40秒;输入0.5 ,脚本在运行时的思考时间为10秒。下面选中此项,输入0.5,再来查看脚本运行结果:
Action.c(111): The duration up to the submit is 0.036610 seconds Action.c(116): Notify: Transaction "注册" ended with "Pass" status (Duration: 131.2022 Think Time: 9.9998 Wasted Time: 0.0366). |
注:将原谅脚本会有0.0002秒的误差。
Use random percentage of recorded think time:使用录制思考时间的百分比。
通过指定思考的时间的范围,可以设置思考时间值的范围。假如思考时间为20秒,这里设置的范围为50% 到100% ,那么脚本运行时候将随即的在这个范围(10秒到20秒之间)内取值。下面再来查看脚本运行时间:
Action.c(111): The duration up to the submit is 0.036191 seconds Action.c(116): Notify: Transaction "注册" ended with "Pass" status (Duration: 40.8002 Think Time: 15.0050 Wasted Time: 0.0362). |
这一次Think Time: 为15.0050,是在10到20秒的范围之内,你再次运行会发现这个时间不一样,但不会超出设定的范围。
Limit think time to:限制思考时间的最大值。
假如脚本设置的思考时间为20秒,选中这一项并设置为10秒。那么脚本在运行时,思考时间不会超过10秒。也就是说这设置同样也制约前面几个选项。这个你可以自行设置验证。
感谢:
double trans_time; //定义变量 ...... lr_start_transaction("注册"); //添加事物 lr_think_time(8); //思考时间 ....... trans_time=lr_get_transaction_wasted_time("注册"); //获得消耗时间 if (trans_time) { lr_output_message("The duration up to the submit is %f seconds",trans_time); //打印输出消耗实时间 }else{ lr_output_message("the duration cannot be determined. "); } lr_end_transaction("注册",LR_AUTO); //事务结束 .......... |
感谢上面这段代码很好帮助我们理解思考时间。
备注:添加思考时间可以更真实的模拟用户行为,但它同时降低了用户并发。也就是说思考时间越长,对服务器的压力会越小。
相关链接:
LoadRunner 技巧之协议分析
LoadRunner 技巧之THML 与 URL两种录制模式分析
Loadrunner的Virtual User Generator 提供人脚本的录制功能,对于初学者来说,这大大的降低了编写脚本的门槛,loadrunner提供两种录制脚本的方式:Html_based script和Url-based script ,初学者疑惑这两种方式有什么不同? 在这里我们来做个简单分析。
下面我们来分别通过两种方式来录制百度首页,对比一下他们之间有什么不同。
HTML方式的脚本:
Action() { web_url("www.baidu.com", "URL=http://www.baidu.com/", "Resource=0", "RecContentType=text/html", "Referer=", "Snapshot=t3.inf", "Mode=HTML", EXTRARES, "Url=http://s1.bdstatic.com/r/www/img/i-1.0.0.png", ENDITEM, "Url=http://s1.bdstatic.com/r/www/img/bg-1.0.0.gif", ENDITEM, "Url=/favicon.ico", "Referer=", ENDITEM, "Url=http://suggestion.baidu.com/su?wd=&cb=window.bdsug.sugPreRequest&sid=1440_2031_1945_1788&t=1362056239875", ENDITEM, "Url=http://suggestion.baidu.com/su?wd=%E8%99%AB%E5%B8%88&p=3&cb=window.bdsug.sug&sid=1440_2031_1945_1788&t=1362056247256", ENDITEM, "Url=http://suggestion.baidu.com/su?wd=%E8%99%AB%E5%B8%88%20&p=3&cb=window.bdsug.sug&sid=1440_2031_1945_1788&t=1362056247969", ENDITEM, "Url=http://suggestion.baidu.com/su?wd=%E8%99%AB%E5%B8%88%20%E5%8D%9A%E5%AE%A2%E5%9B%AD&p=3&cb=window.bdsug.sug&sid=1440_2031_1945_1788&t=1362056251016", ENDITEM, LAST); return 0; } |
URL方式的脚本:
Action() { web_url(www.baidu.com, "URL=http://www.baidu.com/", "Resource=0", "RecContentType=text/html", "Referer=", "Snapshot=t67.inf", "Mode=HTTP", LAST); web_concurrent_start(NULL); web_url("shouye_b5486898c692066bd2cbaeda86d74448.gif", "URL=http://www.baidu.com/img/shouye_b5486898c692066bd2cbaeda86d74448.gif", "Resource=1", "RecContentType=image/gif", "Referer=http://www.baidu.com/", "Snapshot=t68.inf", LAST); web_url("gs.gif", "URL=http://www.baidu.com/cache/global/img/gs.gif", "Resource=1", "RecContentType=image/gif", "Referer=http://www.baidu.com/", "Snapshot=t69.inf", LAST); web_url("tangram-1.3.4c1.0.js", "URL=http://s1.bdstatic.com/r/www/cache/global/js/tangram-1.3.4c1.0.js", "Resource=1", "RecContentType=application/javascript", "Referer=http://www.baidu.com/", "Snapshot=t71.inf", LAST); web_url("home-2.5.js", "URL=http://s1.bdstatic.com/r/www/cache/global/js/home-2.5.js", "Resource=1", "RecContentType=application/javascript", "Referer=http://www.baidu.com/", "Snapshot=t72.inf", LAST); web_url("u-1.3.4.js", "URL=http://s1.bdstatic.com/r/www/cache/user/js/u-1.3.4.js", "Resource=1", "RecContentType=application/javascript", "Referer=http://www.baidu.com/", "Snapshot=t73.inf", LAST); web_concurrent_end(NULL); web_url("i-1.0.0.png", "URL=http://s1.bdstatic.com/r/www/img/i-1.0.0.png", "Resource=1", "RecContentType=image/png", "Referer=http://www.baidu.com/", "Snapshot=t70.inf", LAST); web_concurrent_start(NULL); web_url("su", "URL=http://suggestion.baidu.com/su?wd=&cb=window.bdsug.sugPreRequest&sid=1431_2031_1944_1788&t=1362056977341", "Resource=1", "RecContentType=baiduapp/json", "Referer=http://www.baidu.com/", "Snapshot=t74.inf", LAST); web_url("favicon.ico", "URL=http://www.baidu.com/favicon.ico", "Resource=1", "RecContentType=image/x-icon", "Referer=", "Snapshot=t75.inf", LAST); web_concurrent_end(NULL); return 0; } |
Html_based script是loadrunner的缺省模式,即默认模式,也就是通常说的高层次模式,一般优先选择这种模式这种模式录制的脚本相对简短,便于阅读。它把类属一个页面的请求放在一个web_url中。
Url-based script即通常所说的低层次录制模式,这种模式录制的脚本相对长,不利于阅读,但脚本更直观,它把客户端向服务器端发送的每一个请求都放在一个单独的web_url中,即一个请求对应一个web_url,页面和图片分别生成对应的web_url,相对Html_based script模式把类属一个页面的请求放在一个web_url中的方式,Url-based script模式的脚本更直观。
如何从脚本辨别使用哪种模式录制?
我们可以从脚本web_url中“mode”的值区分,Html_based script模式下mode的值为“html”,Url-based script模式下mode的值为“http”。还有Html_based script模式记录为web_submit_form,而Url-based script模式记录为web_submit_data。
两种录制方式优点对比:
(一)HTML 录制
优点:减少了捕获动态值的需要。
(1)资源从内存中取出且在回放时下载。因此,脚本比其他的录制方式更小且更容易阅读。
(2)由于只有较少的硬编码脚本,因此只有较少的动态数值需要关联。
(3)可以插入图片检查之类的语句以检查结果是否正确。
(4)因为HTML模式回放时需要积极地解析返回的信息,因此它可能会比其他录制模式更加占用资源。然而,HTML模式record/replay有相当大的改善,使得差异最小化且微不足道。
(5)HMTL录制级别会为每一个HTML用户动作产生一个单独的步骤。而且HTML方式产生的脚本非常简洁和直述,易于阅读。
(二)URL 录制
优点:脚本具有灵活性和可量测性。
(1)脚本回放过程中,不再搜索内存和Cache。
(2)脚本更具可扩展性。支持页面上的Java Applets和ActiveX对象。
(3)URL录制级别把对服务器每个对象的请求,都录制成一个单独的请求。对业务过程有更好的控制。
如何选择两种模式?
1、基于浏览器的应用程序推荐使用HTML-Based Script。
2、不是基于浏览器的应用程序推荐使用URL-Based Script。
3、如果基于浏览器的应用程序中包含了Java Script,applet等并且该脚本向服务器产生了请求,比如DataGrid的分页按钮等,也要使用URL-Based Script方式录制。
4、基于浏览器的应用程序中使用了HTTPS安全协议,使用URL-Based Script方式录制。
5、录制过程中不要使用浏览器的“后退”功能,LoadRunner对其支持不太好。
Tips:脚本录制过程中,可以根据需要在HTML级别和URL级别之间灵活地切换,以获得最佳的效果。
相关链接:
LoadRunner 技巧之协议分析
在做性能测试的时候,协议分析是困扰初学者的难题,选择错误的协议会导致Virtual User Generator 录制不到脚本;或录制的脚本不完整,有些应用可能需要选择多个协议才能完整的记录 客户端与服务器端的请求。
最简单的办法就去跑去问开发人员我们的程序用什么协议通讯。当然,有时候为了面子,不好意思去问(也为装X) ,那就只能自己动手去被测系统所使用的协议。
优秀的第三方协议分析工具还是挺多的,如:MiniSniffer 、Wireshark 、Ominpeek 等;当然他们除了帮你分析协议,还提供其它更细致的信息。
好吧,我们只想知道被测系统该用什么协议就够了,那为何不用loadrunner自带的协议分析功工具呢。
第一步
打开LoadRunner ---Virtual User Generator
File(文件) -----> Protocol Advisor(协议分析软件) ----->Analyze Application...(分析应用程序)
弹出Protocol Advisor,是不是发现它和 “开始录制”脚本的对话框长得太像了。
Application type : 选择程序类型。包含两个选项,Internet Applications 一般指B/S的系统,也就是通过浏览器访问的系统;Win32 Applications 一般 C/S 的系统,也就是本地的应用程序,如 QQ。
Program to analyze :选择程序的路径,如果是本地程序(C/S),就找到程序的启动程序。如 QQ的启动程序路径:
"E:\Program Files (x86)\Tencent\QQ\QQProtect\Bin\QQProtect.exe" ;
如果是B/S的体统找到IE浏览器的安装路径。如:
C:\Program Files (x86)\Internet Explorer\iexplore.exe 。
Program arguments : 如果是B/S的系统,请输入要访问的网址(这个网址不要加http://)。如果是C/S则为空。
Working directory : 工具目录,也就是分析信息的保存路径。
第二步
点击OK 开始打应用程序,对应用程序进行操作,loadrunner 将记录你的操作,跟录制脚本是一样一样的。
点击 stop analyzing 停止录制。
第三步
Loadrunner 会返回我们以下信息:
这是一个典型的web 应用,所以,我们用 Web(HTTP/HTML)协议就可以了。
快速测试也是一种测试的方法,它既可以照本宣科的进行,亦可以探索的方式进行。尽管一个使用高度探索性方法进行测试的测试员可能会执行很多快速测试,而快速测试也通常是运用探索性测试方法时的重要因素。但是,快速测试和探索性测试并不是一样的。
快速测试是需要少量时间或一点精力去准备和执行的廉价测试。这类测试甚至不需要具备与待测试的应用程序相关的大量知识或相关的业务领域知识,但它们有助于快速地获取新的信息。快速测试不是强调广泛和完整,它的目的是用最低的成本快速揭示信息。
快速测试是了解产品、识别区域风险及薄弱环节和困惑部分的一个好方法。一个测试员几乎总是在某个测试活动中同时进行一两个其它测试活动。作为最开始的活动,一组快速测试有助于一个冒烟测试或正确测试的进行。几轮相对无计划、非正式的快速测试可以帮助你发现或制定出一个更全面或更正式的测试计划。
在快速软件测试类别中,James Beach 和我提供了许多快速测试的例子。你将会注意到其中的一些例子被成为旅行。注意,并不是所有的旅行都是快速进行的,而且并非所有的快速测试都是旅行。
幸福的路径
以你预计的、终端用户可能进行的方式,从开始到结束完整地执行一个任务。以最简单的、用户最可能的方式、最直截了当的方式使用该产品,正如最乐观的程序员或设计师想象的用户可能进行的方式。寻找可能使一个通情达理的人困惑、迟缓或不快的任何问题。Cem Kaner 有时称这种测试为“认同测试”。 倾向于去了解该产品,而不是发现故障。如果你的确发现明显的问题,那能对于该产品来说是个坏消息。
变化的旅行
使用一个产品,寻找可变因素并使其发生改变。使其在每个层面尽可能不同。如果你使用快速检测的方法来了解,那就要寻找和把重要的变量归类。寻找他们潜在的关系。当我们第一次接触一个产品时,识别和探索变化是我们测试基本结构的一部分。
抽样数据旅行
利用任何你能用到的所有的抽样数据。对于某一种类型的快速测试,最好使用一个简单的值,它的效果是容易理解或计算的。而对于另一种类型的快速测试,选择复杂的或者极端的数据集。观察数据输入的单元或者格式,并且试图改变它们。挑战程序设计器认为会拒绝或者不恰当的数据。一旦你关于合理或者中度质疑数据得到处理,你可以选择尝试去做。。。。。。
攻击性输入
发现输入的来源并企图违反限制的输入。尝试一些异常数据的例子:期望大批输入却输入零;期望正数却使用负数;期望大小合适的数却输入巨大的数;仅支持处理数字的地方使用字母;反之亦然。在一个领域使用几何扩展字符串。双倍长度直到产品崩溃。以不同于你想“正常”或“预期”想法的使用字符。给系统注入任何类型的噪音,看看会发生什么。
人们偏向去讨论非常多的攻击性输入。或许是由于攻击性输入被黑客使用来破坏系统;或许由于攻击性输入相对更直截了当。或许由于它相对比较容易描述。或许由于攻击性输入会产生巨大的意想不到的结果。然而它们绝不是唯一一种快速测试,当然也不是使用探索性方法去测试的唯一方法。
文档旅行
查阅联机帮助或用户手册,并找到一些关于如何完成一些有趣活动的使用说明。明确的做这些。然后通过使用说明即兴发挥并尝试去做。如果你的产品有一个用户说明,那就按照说明操作。你可能发现产品或者文档的问题。无论哪种方式,你已经发现一个潜在的很重要的不一致性。即使你不暴漏一个问题,你仍然会了解这个产品。
文件旅行
看看存在exe 文件的文件夹。检查目录结构,包含的分支。寻找Read ME、帮助文件、日志文件、安装脚步、conf 、ini 、rc文件。审视DLLs的名字,并推断它们可能包含的功能或它们的缺失可能会破坏应用程序的方式。使用任何你有的辅助材料来引导和集中你的行为。另一种为这类测试收集信息的方法是:使用工具去监控这个安装,把工具的输出作为一个起点。
复杂的旅行
使用一个产品,寻找最复杂的特性、最有挑战的数据集、最大的依赖关系。不仅要寻找隐藏的角落和缝隙,而且要寻找程序类似的主要交通区域,繁忙市场,办公大楼,火车站,这些地方有大量的互动,bug 可能混迹于人群之中。
菜单、窗口和会话旅行
使用一个产品并寻找所有的菜单(主菜单和上下文菜单)、菜单选项、窗口、工具栏、图标和其它的控制栏,把所有的都运行一遍,对它们进行分类,或者构建一个思维导图。
键盘和鼠标旅行
使用一个产品,寻找所有你需要用到鼠标和键盘的地方。在键盘上敲击所有的键,敲击所有f键,Enter 键、Tab 键、Esc 、Backspace; 通过字母顺序运行,结合每个键与转变Ctrl 、Alt、 Windows键CMD或其它选项,在其它平台上,欧洲的AltGr键。单击(右,左,左右同时,双击,三重)任何东西。按键的时候与shifted一起按。
中断
开始一个活动并在中途停止它们。在不合适的时候终止它们。使用Cancle按钮,O/S级中断(ctrl-alt-delete或任务管理器)。安排其他程序中断(比如屏幕保护程序或病毒检查)。同时尝试暂停一个活动并返回。把你的笔记本电脑进入睡眠或休眠模式。
破坏
当系统在一个正常的状态,开始使用一个功能,然后改变其状态。当文件处于编辑状态时删除它;弹出磁盘;拔出网线或电源线,通过类似的方法获得机器的不正常状态。这和中断相似,除非你期望功能自己通过检测不再能安全运行来自我中断。
调整
设置一些参数到一个特定的值,然后,在随后的任意时候,在没有重置或重新创建包含文档或数据结构的情况下,重置那个值为其它别的东西。程序员通常期待通过GUI进行对配置和变量进行调整。黑客和有创见的人希望找到其他方法来改变这些值。
机器打桩
不管你在做什么,做更多的事,以及在这些事情上做其它的标注。获得更多的过程;尝试建立更多目前同时存在的状态。调用对话框和非模式对话框嵌套。在多用户系统情况下,让更多的人使用该系统或使用工具模拟这种状态。如果你的测试似乎引发奇怪的行为,在同一个地方堆积,直到让这个异常变得更极端。
连续的使用
测试时,不要重启系统。让窗口和文件打开,让磁盘和内存使用量挂载。你希望随着时间的推移,系统表现出失去了对任务的跟踪或者陷入了困境。
功能相动
发现各个功能互动或共享数据交互的地方。寻找任何相互依赖性。探索它们,开发它们和给它们加压。寻找程序重复的地方或允许你在不同的地方做同样的事情的地方。例如,对于数据显示在不同的方法和不同的地方,寻找不一致性。再比如,把所有的表单中的字段设置到它们的最大值,然后遍历报告生成器。
召唤帮助
在一些操作或活动中使用调出上下文相关的帮助功能。查看这个文件的帮助文件是否有效地解答了问题,或者是否它通过在屏幕的简单重述低估了用户的智商;甚至这种帮助是否切实可用。
疯狂点击
你有没有注意到一只猫或一个孩子可以轻松是系统崩溃?测试不只是“敲打键盘”,但这句话也并非空穴来风。试着去敲打键盘。尽量到处点击。屏幕上每平方厘米都要点击到,直到你找到一个秘密按钮。
鞋测试
对于一个非常廉价的压力测试,使用键盘上的自动重复键。寻找按一个键可用进行的对话框,例如,另一个对话框(可能是一个错误消息),这个对话框的按键与能返回到第一个对话框的按键相关。给键盘穿上鞋并让其行走。让这种测试运行一个小时,如果有一个资源或内存泄露,这种测试可以暴漏它。注意,一些轻量级的自动化可你为你提供虚拟的鞋。
闪烁测试
找出产品的某一个方面,这个方面能产生大量的数据或能够迅速进行一些操作。浏览一个长日志文件过浏览数据库记录,故意滚动太快而看不到细节。注意线长度的趋势,或数据的外观及形状。使用Excel 的条件格式特征来突出数据细胞之间的有趣的差异。软化你的注意力。如果你有一个具有银行显示器的测试实验室,扫描或浏览它们;不正常的行为模式能出人意外的容易识别。
错误消息的遗留
实现功能是对程序员的奖赏。而由于一些错误或例外会导致一个心理问题:标签本身表明某处出错了。人们往往会主动避免思考故障或错误,因此,程序员有时会糟糕地处理一些错误。这时候,使错误信息发生并仔细检测。注意错误和正常路径之间的细微的行为差异。使用自动化使相同的错误出现成千上万次。
资源匮乏
逐步降低内存、磁盘空间、显示分辨率和其他资源。保持产品资源匮乏,直至它崩溃,或者优雅的降低功能(这是我们希望的)
多个实例
同时运行大量的应用实例。开放、使用、更新和保存相同的文件。在不同的窗口操作。注意对资源的竞争和配置。
疯狂配置
用非标准的或非默认的方式来修改操作系统的配置。这种操作在安装这个产品之前或之后都可以进行。打开“高对比度”可访问性模式,或改变本地话默认。改变系统硬盘的字母。把东西放在分默认目录。使用注册表编辑器(用于注册中心条目)或文本编辑器(用于初始化文件)的方式来破坏你程序的设置,这都将引发一个错误消息,恢复或引出一个正常的默认行为。
重述:快速测试有可能是高度探索性的,但它们只是探索性测试的一个种类。不要轻信快速测试或某些类型的快速测试都是探索性测试。
相关链接:
探索性测试(一):探索性测试不是游览
探索性测试(二):探索性测试不是终结性测试
探索性测试(三):探索性测试并不是不使用工具的测试
当描述代码之类的东西时,我不喜欢 “气味(smell)”这个词。因为用拟人的手法来谈论比特和字节往往令人觉得很怪异。并不是说“气味”这个词不能准确地反映出某种表明代码可能有错误的症状,只是我觉得这样听起来很滑稽。然而,我依然选择再次用这种令人厌烦的方式来描述软件构建,坦白说,这是因为这些年我见过的很多构建脚本都散发着难闻的气味。 在创建构建脚本时,即使是伟大的程序员也常常会遇到困难。就好像最近才学会如何编写程序性 代码似的 —— 他们还会编写庞大的单块构建文件、通过复制-粘贴编写代码、对属性进行硬编码等等。我总是很想知道为什么会这样。也许是因为构建脚本没有被编译成客户最终会使用的东西?然而我们都知道,要创建客户最终使用的代码,构建脚本是中心,如果那些脚本败絮其中,那么要想有效地创建 软件,就需要克服重重挑战。
幸运的是,您可以轻松地在构建(不管是 Ant、Maven 还是定制的)之上部署一些实践,它们虽然可以帮助您创建一致的、可重复的、可维护的构建,但其过程会很长。学习如何创建更好的构建脚本的一种有效的方法是搞清楚哪些事情不要 去做,理解其中的道理,然后看看做事的正确 方法。在本文中,我将详细论述您应该避免的 9 种最常见的构建中的气味,为什么应该避免它们,以及如何修复它们:
惟 IDE 的构建
复制-粘贴式的编写脚本方法
冗长的目标
庞大的构建文件
没有清理干净
硬编码的值
测试失败还能构建成功
魔力机
格式的缺失
这里无意给出完整的列表,不过这份列表的确代表了近年来我读过的和写过的构建脚本中,我遇到的较为常见的一些气味。有些工具,例如 Maven,是为处理与构建有关的很多管道而设计的,它们可以帮助减轻部分气味。但是无论使用什么工具,还是有很多问题会发生。
避免惟 IDE 的构建
惟 IDE(IDE-only)的构建是指只能通过开发人员的 IDE 执行的构建,不幸的是,这似乎在构建中很常见。惟 IDE 的构建的问题是,它助长了 “在我的计算机上能运行”问题,即软件在开发人员的环境中可以运行,但是在任何其他人的环境中就不能运行。而且,由于惟 IDE 构建自动化程度不是很高,因而为集成到持续集成(Continuous Integration)环境带来极大的挑战。实际上,没有人为的干预,惟 IDE 常常无法自动化。
我们要清楚:使用 IDE 来执行构建并没有错,但是 IDE 不应该成为能构建软件的惟一环境。特别是,一个完全用脚本编写的构建,可以使开发团队能够使用多种 IDE,因为只存在从 IDE 到构建的依赖性,而不存在相反方向的依赖性,如图 1 所示:
图 1. IDE 与构建的依赖关系
惟 IDE 的构建有碍自动化,清除的惟一方法就是创建可编写脚本的构建。有足够的文档和太多的书籍可以为您提供指导(见 参考资料),而像 Maven 之类的项目也为从头开始定义构建提供了极大的方便。不管采用何种方法,都是选择一种构建平台,然后尽快地让项目成为可编写脚本的。
复制-粘贴就像廉价的香水
复制代码是软件项目当中一个常见的问题。实际上,甚至很多流行的开放源码项目都存在 20% 到 30% 的复制代码。代码复制令软件程序更难于维护,同理,构建脚本中的复制代码也存在这样的问题。例如,想象一下,假设您需要通过 Ant 的 fileset 类型引用特定的文件,如清单 1 所示:
清单 1. 复制-粘贴 Ant 脚本
<fileset dir="./brewery/src" > <include name="**/*.java"/> <exclude name="**/*.groovy"/> </fileset> |
如果需要在其他地方引用这组文件,例如为了编译、检查或生成文档,那么最终您可能会在多个地方使用相同的 fileset。如果在将来某个时候,您需要对那个 fileset 做出修改(比如说排除 .groovy 文件),那么最终可能需要在多个地方做更改。显然,这不是可维护的解决方案。然而,要除掉这股气味其实很简单。
如清单 2 所示,通过 Ant 的 patternset 类型可以引用一个逻辑名称,以表示所需要的文件。那么,当需要向 fileset 添加(或排除)文件时,只需更改一次。
清单 2. 复制-粘贴 Ant 脚本
<patternset id="sources.pattern"> <include name="**/*.java"/> <exclude name="**/*.groovy"/> </patternset> ... <fileset dir="./brewery/src"> <patternset refid="sources.pattern"/> </fileset> |
对于精通面向对象编程的人来说,这种修复方法看上去很熟悉:既定的惯例不是在不同的类中一次又一次地定义相同的逻辑,而是将那个逻辑放在一个方法中,在不同地方都可以调用这个方法。于是,这个方法成为惟一的维护点,从而可以限制错误级联并可以鼓励重用。
不要掺入冗长目标的气味
Martin Fowler 在他撰写的 Refactoring 这本书中,对代码中存在冗长方法的气味这个问题做了精妙的描述 —— 过程越长,越难理解。实际上,冗长方法最终会担负太多的责任。当谈到构建时, 冗长目标这种构建气味是指更难于理解和维护的脚本。清单 3 就展示了一个相当冗长的目标:
清单 3. 冗长目标
<target name="run-tests"> <mkdir dir="${classes.dir}"/> <javac destdir="${classes.dir}" debug="true"> <src path="${src.dir}" /> <classpath refid="project.class.path"/> </javac> <javac destdir="${classes.dir}" debug="true"> <src path="${test.unit.dir}"/> <classpath refid="test.class.path"/> </javac> <mkdir dir="${logs.junit.dir}" /> <junit fork="yes" haltonfailure="true" dir="${basedir}" printsummary="yes"> <classpath refid="test.class.path" /> <classpath refid="project.class.path"/> <formatter type="plain" usefile="true" /> <formatter type="xml" usefile="true" /> <batchtest fork="yes" todir="${logs.junit.dir}"> <fileset dir="${test.unit.dir}"> <patternset refid="test.sources.pattern"/> </fileset> </batchtest> </junit> <mkdir dir="${reports.junit.dir}" /> <junitreport todir="${reports.junit.dir}"> <fileset dir="${logs.junit.dir}"> <include name="TEST-*.xml" /> <include name="TEST-*.txt" /> </fileset> <report format="frames" todir="${reports.junit.dir}" /> </junitreport> </target> |
这个冗长的目标(相信我,我还见过冗长得多的目标)要执行四个不同的过程:编译源代码、编译测试、运行 JUnit 测试和创建一个 JUnitReport。要担负的责任已经够多了,更不用说将所有 XML 放在一个地方所增加的相关的复杂性。实际上,这个目标可以拆分成四个不同的、逻辑上的目标,如清单 4 所示:
清单 4. 提取目标
<target name="compile-src"> <mkdir dir="${classes.dir}"/> <javac destdir="${classes.dir}" debug="true"> <src path="${src.dir}" /> <classpath refid="project.class.path"/> </javac> </target> <target name="compile-tests"> <mkdir dir="${classes.dir}"/> <javac destdir="${classes.dir}" debug="true"> <src path="${test.unit.dir}"/> <classpath refid="test.class.path"/> </javac> </target> <target name="run-tests" depends="compile-src,compile-tests"> <mkdir dir="${logs.junit.dir}" /> <junit fork="yes" haltonfailure="true" dir="${basedir}" printsummary="yes"> <classpath refid="test.class.path" /> <classpath refid="project.class.path"/> <formatter type="plain" usefile="true" /> <formatter type="xml" usefile="true" /> <batchtest fork="yes" todir="${logs.junit.dir}"> <fileset dir="${test.unit.dir}"> <patternset refid="test.sources.pattern"/> </fileset> </batchtest> </junit> </target> <target name="run-test-report" depends="compile-src,compile-tests,run-tests"> <mkdir dir="${reports.junit.dir}" /> <junitreport todir="${reports.junit.dir}"> <fileset dir="${logs.junit.dir}"> <include name="TEST-*.xml" /> <include name="TEST-*.txt" /> </fileset> <report format="frames" todir="${reports.junit.dir}" /> </junitreport> </target> |
可以看到,由于每个目标只担负一种责任,清单 4 中的代码理解起来要容易得多。根据用途分离目标,不但可以减少复杂性,还为在不同上下文中使用目标创造了条件,必要时还可以重用。
庞大的构建文件也有一种很重的气味
Fowler 还将 庞大的类也看作一种代码气味。就构建脚本而言,有这种类似气味的就是庞大的构建文件,它相当难以读懂。很难知道哪个目标是做什么的,目标的依赖关系是什么。这同样会给维护带来问题。而且,庞大的构建文件通常有相当多的剪切-粘贴的痕迹。
为了缩小构建文件,可以从脚本中找出逻辑上相关的部分,将它们提取到更小的构建文件中,由主构建文件来执行这些较小的构建文件(例如,在 Ant 中,可以使用 ant 任务调用其他构建文件)。
通常,我喜欢根据核心功能拆分构建脚本,确保它们可以作为独立脚本来执行(想想构建组件化)。例如,在我的 Ant 构建中,我喜欢定义四种类型的开发者测试:单元、组件、系统和功能。而且,我还喜欢运行四种类型的自动检查工具:编码标准、依赖性分析、代码覆盖范围和代码复杂度。我不是将这些测试和检查工具的执行放在一个庞大的构建脚本中(还加上编译、数据库集成和部署),而是将测试和检查工具的执行目标提取到两个不同的构建文件中,如图 2 所示:
图 2. 提取构建文件
更小、更简洁的构建文件维护和理解起来要容易得多。实际上,这种模式对于代码而言同样适用。我们似乎在这里看到了模式的概念,不是吗?
没有清理
没有严格减少所有底层假设的构建无疑是一颗定时炸弹。例如,如果构建没有避免一些简单的假设,例如会去掉用陈旧的数据生成的二进制文件,那么前一次构建遗留下来的文件就会引起错误。或者,正是由于前一次构建留下的文件,构建竟然得以"成功",这种情况更糟糕。
幸运的是,这个问题的解决办法很直观:只需删除任何之前的构建留下的所有目录和文件,就可以很容易地消除假设。这个简单的动作就可以减少假设,保证构建的成功或失败都是正确的。清单 5 演示了通过使用 delete Ant 任务删除之前的构建所使用的所有文件或目录,从而清理构建环境的一个例子:
清单 5. 事先清理
<target name="clean"> <delete dir="${logs.dir}" quiet="true" failonerror="false"/> <delete dir="${build.dir}" quiet="true" failonerror="false"/> <delete dir="${reports.dir}" quiet="true" failonerror="false"/> <delete file="cobertura.ser" quiet="true" failonerror="false"/> </target> |
众所周知,旧的构建遗留下来的文件会导致很多不必要的麻烦。为了自己的方便,在运行一个构建之前,务必先删除构建所创建的任何工件。
硬编码的臭味
复制-粘贴式的编程有碍重用,将值进行硬编码又何尝不是呢。当构建脚本包含硬编码的值时,如果某个方面需要修改,那么就需要在多个地方修改那个值。更糟糕的是,很可能会忽略了某个地方而没有改那个值,从而引起与不匹配的值相关的错误,这种错误是很隐蔽的。而且,如果相信我的建议,选择使用多个构建脚本,那么硬编码的值将可能会成为构建维护中最终的挑战。在这一点上也请相信我!
例如,在清单 6 中,run-simian 任务有很多硬编码的路径和值,即 _reports 目录:
清单 6. 硬编码的值
<target name="run-simian"> <taskdef resource="simiantask.properties" classpath="simian.classpath" classpathref="simian.classpath" /> <delete dir="./_reports" quiet="true" /> <mkdir dir="./_reports" /> <simian threshold="2" language="java" ignoreCurlyBraces="true" ignoreIdentifierCase="true" ignoreStrings="true" ignoreStringCase="true" ignoreNumbers="true" ignoreCharacters="true"> <fileset dir="${src.dir}"/> <formatter type="xml" toFile="./_reports/simian-log.xml" /> </simian> <xslt taskname="simian" in="./_reports/simian-log.xml" out="./_reports/Simian-Report.html" style="./_config/simian.xsl" /> </target> |
如果硬编码 _reports 目录,那么当我决定将 Simian 报告放到另一个目录时,就会很麻烦。而且,如果其他工具在脚本的其他地方使用这个目录,那么很可能会有人输错目录名称,导致报告显示在不同的目录中。这时可以定义一个属性值,由这个属性值指向这个目录。然后,在整个脚本中都可以引用这个属性,这意味着当需要更改的时候,只需光顾一个地方,即属性的定义。清单 7 展示了重构之后的 run-simian 任务:
清单 7. 使用属性
<target name="run-simian"> <taskdef resource="simiantask.properties" classpath="simian.classpath" classpathref="simian.classpath" /> <delete dir="${reports.simian.dir}" quiet="true" /> <mkdir dir="${reports.simian.dir}" /> <simian threshold="${simian.threshold}" language="${language.type}" ignoreCurlyBraces="true" ignoreIdentifierCase="true" ignoreStrings="true" ignoreStringCase="true" ignoreNumbers="true" ignoreCharacters="true"> <fileset dir="${src.dir}"/> <formatter type="xml" toFile="${reports.simian.dir}/${simian.log.file}" /> </simian> <xslt taskname="simian" in="${reports.simian.dir}/${simian.log.file}" out="${reports.simian.dir}/${simian.report.file}" style="${config.dir}/${simian.xsl.file}" /> </target> |
硬编码的值不仅没有提高灵活性,反而拟制了灵活性。就像在源代码中很容易硬编码数据库连接 String 一样,在构建脚本中也应该避免将路径之类的东西硬编码。
测试失败时,构建却能成功
构建远远不止于单纯的源代码编译,它还可能包括自动化开发者测试的执行,如果想让软件一直正常运行,那么决不能允许构建中有任何失败的测试。别忘了,如果测试都得不到信任,那么还要测试干什么呢?
清单 8 是这种构建气味的一个例子。注意 junit Ant 任务的 haltonfailure 属性被设置为 false(它的缺省值)。这意味着即使任何 JUnit 测试是失败的,构建也不会失败。
清单 8. 气味:测试失败,构建却成功
<junit fork="yes" haltonfailure="false" dir="${basedir}" printsummary="yes"> <classpath refid="test.class.path" /> <classpath refid="project.class.path"/> <formatter type="plain" usefile="true" /> <formatter type="xml" usefile="true" /> <batchtest fork="yes" todir="${logs.junit.dir}"> <fileset dir="${test.unit.dir}"> <patternset refid="test.sources.pattern"/> </fileset> </batchtest> </junit> |
有两种方法防止构建中的这种气味。第一种方法是将 haltonfailure 属性设置为 true。这样就可以防止测试失败构建却成功的情况发生。
对于这种方法,我惟一不喜欢的地方是,我想看看有多大百分比的测试遭到了失败,以便弄清楚失败的模式。因此第二种方法就是,每当有测试失败,就设置一个属性。然后,我对 Ant 进行配置,使得当执行了所有的测试之后,构建最终失败。这两种方法都行之有效。清单 9 演示了使用 tests.failed 属性的第二种方法:
清单 9. 测试令构建失败
<junit dir="${basedir}" haltonfailure="false" printsummary="yes" errorProperty="tests.failed" failureproperty="tests.failed"> <classpath> <pathelement location="${classes.dir}" /> </classpath> <batchtest fork="yes" todir="${logs.junit.dir}" unless="testcase"> <fileset dir="${src.dir}"> <include name="**/*Test*.java" /> </fileset> </batchtest> <formatter type="plain" usefile="true" /> <formatter type="xml" usefile="true" /> </junit> <fail if="tests.failed" message="Test(s) failed." /> |
适用范围
测试活动大概可分为两部分:发现产品的缺陷,验证产品无缺陷,两者缺一不可。探索测试可用于缺陷尽快、尽早的暴露,给我们大概勾画出缺陷分布图,但不能保证产品无瑕疵。故此法必须配合老老实实的规格验收活动,探索测试可作为敏捷开发的一个小活动,产品质量由下自上改进一个添花项。
技术积累
缺陷分布的宏观分析:各个版本按照一定的模板收集缺陷类型(ST阶段数据为佳);结合逆向分析、网上问题分析,给出我们产品的薄弱点。
具体版本关键路径分析:结合版本设计要素,管理好关键风险;建设好规格的验收活动,必须是自动化的,最好是可重用的;UT、IT、ST用例是完整的,SDV验收活动是可靠的。
功能模块的解耦合,软件硬件解耦合便于各个子领域的质量控制;集成风险的分析,有效的控制产品的质量。
活动开展模式
规格分解阶段,分析规格(此规格是TSEG与SEG共同确认的),建设规格验收的活动。编码阶段,测试人员白天分析代码,编写测试用例,晚上自动化验证。两组人马可以相同,两项活动必须开展,相辅相成。
现在资源分析
接入产品线的老特性的自动化验收用例是比较完整的,但缺乏具体的覆盖统计。骨干员工基本上接触过脚本语言,但不太熟练,若新特性的研发的自动化规格验证脚本,探索测试发现的用例自动化实现,能否及时交付,可能存在一定的难度。组内做软件的人员基本上没有开发经验,软硬系统架构能力缺乏,能否有效的分析出关键风险,有效的控制好软件硬件解耦以及系统集成,存在很大的问题。
接入产品现有的体现结构还相对简单,就目前的人力来看,若能有效结合软硬各自领域的知识,TSEG可以支撑白盒测试的开展。
团队测试文化建设
落实测试基本理念:发现产品的缺陷,验证产品无缺陷,两者缺一不可。测试人员对负责的特性负责,而不是仅仅是交付件。
推进计划
第一步,能力准备与数据准备。代码分析能力提升与自动化测试用例设计能力提升:可以从维护版本开始,在缺陷修改阶段,研发测试共同讨论修改方案;测试提供验证用例,必须是自动化的,这样白天设计用例,晚上自动化验证。从转测试后的版本质量评估此项活动是否有效。衡量目标,维护版本只需一个质量验收版本即可发布;缺陷分布因子确定,需要从各个版本中统计出缺陷的分布情况与遗留到网上去的缺陷分布情况。
第二步,在新版本上试点,规格分解后,QX接口确定后,同步开发规格自动化验收用例。开发阶段分层覆盖QX接口,新增函数路径覆盖,软硬接口文档。若版本的ST测试质量能达到如上的覆盖要求,而SDV阶段规格自动化验收集合是完备的(是指精确的知道自动化覆盖多少规格以及场景,手工用例覆盖了多少规格以及场景)。则在SDV阶段可以采用探索测试的方法,激发员工的能动性。
第三步,在ST阶段采用探索测试方法,快速的找到产品的短木板(关键)。具体实现还没有想清楚,请给位指点迷津。
前景展望
测试的劣势在重复性的工作太多,同时这也是我们的优势,只要我们能找到重复到重用的道路。今后我们有理由相信,测试的工作是可以达到24*7的,我们只要做好8*5的设计工作,其余的就交给工具来帮我们完成。
流程测试是测试人员把系统各个模块连贯起来运行、模拟真实用户实际的工作流程,满足用户需求定义的功能来进行测试的过程。
业务流程测试是系统测试最重要的内容,而测试的依据就是用户定义的需求和测试人员的测试设计,因此下面就从需求、测试设计、测试执行等角度上重点来阐述如何做好业务流程测试。
一、关注需求和用户
1、站在用户的角度
优秀的需求应该是站在用户的角度来思考问题,是用户能够利用系统完成什么,而不是系统自己完成。因此在需求理解时要多和软件的最终用户进行交流,了解他们的诉求,以便有针对性的进行测试。
2、重视全局,而非细节
工作重点应该是放在尽可能全面的收集需求要点、了解整体的业务流程、分析主体业务流程和重点业务流程等工作上。在获得了系统的全貌之后,我们会发现原先在编写功能测试用例对系统的认识是不充分的,这时要编写的流程测试用例需要根据新的思路进行重新排列。
3、现场客户
现场客户随时提供对需求细节的指导。如果没有条件,可以定期的邀请用户参加项目例会或安排和用户交流等。另外在需求理解评审和测试设计评审会尽量邀请用户参与。
二、精心设计流程用例
1、流程用例编写要点
● 要有基本数据,以便系统测试多次使用,同时方便自动化工具介入。
● 其他流程要依赖这套数据,使之每个流程可以更有针对性的执行。
● 构建的数据要尽量有具体的意义,严禁用a、b、c;1、2、3等
● 流程要符合用户常用的业务操作习惯,尽量考虑用户的实际操作去编写。
● 流程可大可小,但每一个流程都要是一个典型的业务操作。
● 流程不必覆盖到所有功能点,因为流程用例是功能用例的一个补充。
● 流程不要被具体的模块所限制,各个模块可以交叉。用户实际的业务操作是没有界限的。
2、流程用例编写实践
● 系统总流程表
该表制定的目的首先是理清系统脉络,和编写者的思路;其次是给后进入项目的tester,一个对系统大概的认识,对于系统的功能和各个模块之间的关系有个宏观的认识。
● 角色功能表
因为我们现在所做的系统大都是多用户多权限的,对应不同角色有不同的权限。包括菜单级和操作级的。比如E-Sales系统中就有8种角色50多种权限,所以有一个清晰的列表对于用户理解和测试系统是有很大帮助的,在测试不同角色对应的不同功能页面或操作可以通过该表进行二维的对应。
● 测试数据列表
流程测试要依赖一套可以重用的并且尽量符合用户实际操作的数据。测试用例中包含精心准备的数据,在执行时会有的放矢,更贴近用户的操作。
● 流程测试用例表
这是最重要的一个部分,是我们测试流程的出发点和根据,和功能测试用例不同的是,
我们这里所关注的是业务操作的流程,编写时参照“流程用例编写要点”。
流程测试用例编写参照流程测试模版及案例。
三、测试执行
● 在系统测试每轮测试保持测试数据库都是完整的一套初始数据,通过exp/imp实现;
● 在数据稳定、界面稳定的前提下通过自动化工具录制流程测试脚本;现在部门推荐MI公司WinRunner和LoadRunner。
● WinRunner使用参照vss中测试组整理的WinRunner7.6使用指南
LoadRunner使用参照vss中测试组整理的LoadRunner 压力测试实例
一、业务流程整理
1、充分掌握业务知识,业务流程以及业务的数据流向。
站在用户的角度思考,而不仅仅考虑在系统中如何操作业务流程;搞清楚每一项业务中的详细流程和各个环节涉及的角色,一项比较复杂的业务其详细流程往往比较多,只有了彻底掌握了这项业务,才能对当前业务环节进行全方位的测试。
2、从需求人员或者客户那里了解到各业务流程的重要程度和使用频率。(这点对把握测试重点很重要)
3、了解业务流程在系统中对应的功能。(建立业务与系统的映射,为编写测试用例做好准备)
二、编写测试用例(在需求文档以及UI原型评审之后)
1、绘制业务流程图(对于较简单的流程,也可以用文字描述的形式,但流程图比较直观,也便于进行路径的分析)。
2、根据业务流程的重要程度、使用频率为各流程设置好优先级。
3、采用场景法、路径法或其他方法(方法其实是不固定的,有时候可以综合使用多种方法)梳理出每个业务流程在系统中对应的操作步骤,形成业务流程的测试用例。
注意:
* 这里的操作步骤没有必要像功能点测试用例的步骤那么详细,这个操作步骤可能是一个业务操作集,可以分解成多个步骤,这些业务操作集合,也可以对应具体的功能点测试用例,从而做到测试用例的复用。所以可以说这里的业务流程测试用例就像是将多个功能点的测试用例组合成一个集合,形成一个业务流。
* 在每个步骤中需要标识出执行该操作的用户角色,因为在一个业务流程中,很可能涉及到不同的角色。
* 需要平衡项目的进度、成本,不一定需要覆盖所有的路径。
三、测试数据设计
1、输入数据:
测试业务流程与功能点测试的重点不一样,因此设计测试数据的时候更多需要考虑下面的因素(按重要到次要排列):
1)关键的判断条件
2)符合业务意义的数据
3)边界数据
4)异常数据
另外,对流程无任何影响的数据,我认为可以在此不考虑,放到功能点测试中更加合适,这样可以减少不必要的干扰。不过,有些功能点对流程的依赖很强,或者业务流程非常简单,也可以将业务流程测试与功能点测试结合。(实际我觉得功能点测试与业务流程测试的数据分开会好一点,因为毕竟重点不一样;但有时迫于进度的压力,也会将这些数据结合在一起)
2、输出数据:
系统中得到的结果数据以及报表中的数据,都需要体现出来,必要的时候还需要根据报表的格式提供输出数据,以便在测试时进行核对。
注意:需要平衡项目的进度、成本,尽可能用少的测试数据发现多的问题。
四、测试执行
主要在下面几个阶段执行业务流程测试:
1、最主要是在系统测试阶段进行(将优先级高的主要业务流程测试用例作为冒烟测试用例)。
2、在集成测试的后期,已经对部分业务测试流程进行了测试,可以根据系统集成的顺序,在集成测试阶段对部分业务流程进行测试。集成测试阶段重点是测试功能点,功能点测试存在严重问题,是无法进行业务流程测试的,所以一般是等功能比较稳定的时间才会进行业务流程测试。
3、验收测试。
4、个人观点:保证质量最有力的手段还是预防,如果能够将业务流程测试用于测试的前期,比如:用于开发人员进行联调、或者送测前的测试,这样可能会提高送测质量,减少测试轮次,提高编码质量。
相关链接:
业务流程测试总结
摘要:随着现代网络的发展速度不断提高,B/S结构的程序软件需求越来越普遍,而各公司的业务需求量也在不断增大,因此对软件并发需求及吞吐量需求也越来越大,这就要求各软件企业在应对用户性能需求时使用更有效的测试策略,论文就当前软件企业性能测试策略现状进行了归纳分析。
关键词:性能测试;测试策略;测试分析
一、引言
现代公司用户使用的应用型软件大多数为B/S结构和C/S结构,而随着公司用户及客户数量的增加,应用软件对系统并发用户数要求越来越高,同时,大多数在线系统用户对注册时间,响应时间要求也较高,因此,大部份公司对软件企业设计的软件项目在最大负载及压力需求上都有直接的用户需求,在进行软件项目需求分析及项目设计过程中,软件企业要充分考虑到软件项目在后期维护过程中客户的需求变化及维护费用带来的公司利润,大多数软件企业都更加注重项目在发布前的性能测试工作,性能测试在软件企业及其它项目开发者使用专门的软件测试工具及脚本描述测试对象与相关性能指标的特征并对软件性能进行评价的过程。在性能测试的过程中,有的公司采用的是商业测试工具,有的公司采用的是开源工具,还有的公司是采用自己开发的测试工具进行操作评价,并对开发人员进行沟通,从而对软件项目质量进行综合评价。
二、性能测试工具分析
性能测试要求软件企业在模拟用户真实环境的场景下对软件项目进行客观的评价,而在软件企业中单凭固定的硬件设备往往不能达到模拟并发用户,模拟多个负载的场景,因而,性能测试工具就变得格外重要。目前软件企业的性能测试工具大体分为以下几种:
1、开源工具
象部分Linux操作系统内核及IBM公司投资开发的eclipse一样,使用灵活,功能全面,免费的开源工具是大多数软件公司的首选。目前做性能测试的开源工具主要有Jmeter,针对Java Web程序项目开发及FTP服务项目进行性能测试,通过模拟多个并发的虚拟用户,通过集合点、事务点对场景进行负载测试、压力测试,提供图形化的显示界面对软件项目性能进行综合分析;DbMonster,主要针对Sqlsever数据库进行压力测试;TpTest,主要对internet网络连接速度、响应时间进行测试。
2、商业工具
商业工具往往由专门的公司进行开发,通过大量的人力及资金投入支持,开发后一般具有界面更加美观、操作更加方便、功能更加强大、支持的插件更多等特点。并且因为商业软件的本身特点,一般还包括后期的升级服务,用户操作掊训服务等优势。所以也广泛地应用于各大型企业。目前主要的商业工具有HP公司收购Mercury后继续提供的LoadRunner,具有支持负载多,支持协议多,实时进行性能指标跟踪的特点;IBM公司开发的Rational系列Performance Tester适用于团队开发,通过性能测试从创建到结果分析的自动化过程对软件项目进行性能分析。
3、免费工具
免费工具因为免费使用,操作方便,针对性强等特点也深受中小型软件企业喜爱,大多数公司还在免费工具的基础上,继续改进从而开发针对于本公司产品特点的性能测试工具,这样使得成本更低,效率更高。目前主要的性能测试免费工具有针对于windows应用程序的`Center Test及针对于web程序的Stress Tool。
三、性能测试在软件企业的应用分析
随着信息化的发展速度,包括中国大陆在内的软件行业正处于高速发展的过程中。越来越多的企业更加注重软件项目产品的质量,性能测试已成为软件项目质量评价的一个重要指标,直接关系到软件企业的宏观发展、声誉影响及公司效率。因此针对本身企业的发展,不同的软件企业必须采用适合自己的应用策略。
1、大型软件企业的应用策略分析
大型企业的软件系统往往并发用户数较多,用户功能复杂,用户对响应时间要求高,企业对软件系统的安全性、稳定性要求高。同时,大型企业往往也存在资金雄厚、设备先进,更新速度快,开发人员技术实力雄厚,对系统升级带来的适应性更强,对于这样的企业,为了保证公司业务流程的广泛开展,对于软件项目可以采取构建自己的专业团队进行性能测试或将性能测试外包给有实力的软件测试公司来完成。软件项目的测试从国外的经验看,测试工作可能由除开发方和使用方外的第三方公司进行开展,这样不仅可以脱离本身开发或使用角度的片面性导致测试用例设计不全面,考虑不周全带来的BUG影响,同时可以使得开发团队与测试团队互相协作,提高软件开发的质量。
2、中、小型软件企业的应用策略分析
中小型企业业务流程相对简单,用户有并发需求,对响应时间也有具体的容忍度,但软件项目功能相对简单,负载及压力性能指标要求也相对较低,同时大多数企业对成本的要求期望也较高,投资相对较小。当然,只要是软件产品,都必尽量在克服资金限制的情况下使软件项目发挥最大功能,满足用户需求。对于中小型企业的性能测试特点,可以通过组建临时性能测试团队、自行编制脚本程序或能过专门的测试机构进行性能测试,在公司技术实力相对较强的情况下,可以通过组织10至30人的测试团队进行性能测试,在进行性能测试时可以借助免费工具或开源工具进行工作开展,如果公司技术相对较弱,时间紧,则可借助专门的测试机构进行性能测试工作。
四、小结
随着农村信息化的发展速度,企业国际化的发展模式不断向前推进,软件项目的开发需求将会越来越大,而大多数企业从过去的注重数量变得更加注重产品的质量及产品的后期服务,良好的性能测试策略已渐渐成为优秀软件项目的重要指标,软件企业应该根据企业自身的特点制定适合自己的性能测试策略,通过中长期规划建立良好的性能测试质量保障体系。
摘要:根据以往软件测试经验,对数据库测试的内容和方法,进行了详细的分析,阐明了数据库测试在软件开发中的重要性。
关键词:数据库测试;性能测试;DataFactory
1、引言
数据库系统的开发在应用软件开发中所占的比重越来越大,随之而来的问题也越来越突出。比如:数据冗余,功能和性能方面存在的问题已经严重影响应用软件的使用。软件测试人员往往重视对软件功能和编码的测试,而忽略对软件性能,特别是数据库访问并发测试。因为,他们固有的思想中认为数据库设计存在问题对系统性能影响不大,或从根本上忽略了数据库在软件开发中的地位,直到出现了问题,才想到对数据库的测试,但往往也是仅仅通过对编码的测试工作中捎带对数据库进行一定的测试,这远远是不够的。目前,中铁网上订票系统在大用户同时在线订票中系统频频瘫痪,就是最好的佐证。
所以,在应用软件的测试工作中,应该将数据库作为一个独立的部分进行充分的测试,这样才可以得到应用软件所需要的性能优化的数据库。那么,应该对哪些内容进行测试,如何进行测试呢?
2、数据库设计的测试
数据库是应用的基础,其性能直接影响应用软件的性能。为了使数据库具有较好的性能,需要对数据库中的表进行规范化设计。规范化的范式可分为第一范式、第二范式、第三范式、BCNF范式、第四范式和第五范式。一般来说,逻辑数据库设计应满足第三范式的要求,这是因为满足第三范式的表结构容易维护,且基本满足实际应用的要求。因此,实际应用中一般都按照第三范式的标准进行规范化。但是,规范化也有缺点:由于将一个表拆分成为多个表,在查询时需要多表连接,降低了查询速度。故数据库设计的测试包括前期需求分析产生数据库逻辑模型和后期业务系统开发中的测试两部分(这里指的是后者),我在这里称为实体测试。
数据库是由若干的实体组成的,包括(表,视图,存储过程等),数据库最基本的测试就是实体测试,通过对这些实体的测试,可以发现数据库实体设计得是否充分,是否有遗漏,每个实体的内容是否全面,扩展性如何。
实体测试,可以用来发现应用软件在功能上存在的不足,也可以发现数据冗余的问题。经过测试,测试人员对有异议的问题要及时和数据库的设计人员进行沟通解决。
3、数据一致性测试
在进行实体测试后,应进一步检查下面的内容以保障数据的一致性:
3.1 表的主键测试根据应用系统的实际需求,对每个表的主键进行测试,验证是否存在记录不唯一的情况,如果有,则要重新设置主键,使表中记录唯一。
3.2 表之间主外键关系的测试数据库中主外键字段在名称,数据类型,字段长度上的一致性测试。
3.3 级联表,删除主表数据后,相应从报表数据应同时删除的问题例如学生表和学生成绩表,学生数据已经删除,成绩表中相应学生的成绩记录应同时删除。
3.4 存储过程和触发器的测试存储过程可以人工执行,但触发器不能人工处理,所以在对存储过程和触发器执行的过程中针对SQL SERVER2005及以上版本可以使用Microsoft SQL Server Profiler性能测试工具进行测试。
Microsoft SQL Server Profiler 是 SQL 跟踪的图形用户界面,用于监视数据库引擎或 Analysis Services 的实例。测试人员可以捕获有关每个事件的数据并将其保存到文件或表中供以后分析。例如:可以对生产环境进行监视,了解哪些存储过程由于执行速度太慢影响了性能。
4、数据库的容量测试
随着数据库系统的使用,数据量在飞速增长,如何在使用前对数据容量的增长情况进行初步估算,为最终用户提供参考,这在数据库使用和维护过程中,是非常重要的。可以通过对数据库设计中基本表的数据大小,和每天数据表的数据产生量进行初步估算。
记录数据量=各个字段所占字节数的总和
表的数据量=记录数据量*记录数
数据库大小=各表数据量的总和
当然,数据库的大小不仅仅只是基本表的大小,还有系统表,视图,存储过程等其它实体所占的容量,但最基本的数据是表的数据。另外,数据库的容量还包括数据库日志文件的容量,一般应预留数据库文件的2倍左右。
5、数据库的性能测试
应用软件除了功能外,很重要的一部分就是软件的性能,而对于数据库系统,数据库性能的好坏会直接影响应用软件的性能,这部分的测试,一般手工测试就显得无能为力了,这时就要借助自动化的测试软件,例如:DataFactory,DataFactory是一种强大的数据产生器,它允许开发人员和测试人员很容易产生百万行有意义的正确的测试数据库,该工具支持DB2、Oracle、Sybase、SQL Server数据库。这样,就可以模拟出应用软件长期使用后,海量数据存储的数据库的性能状况。从而尽早发现问题,进行数据库性能的优化。
这里要注意,进行性能测试的时候,一定要注意测试环境的一致性,包括:操作系统、应用软件的版本以及硬件的配置等,而且在进行数据库方面的测试的时候一定要注意数据库的记录数、配置等要一致,只有在相同条件下进行测试,才可以对结果进行比较。否则无法和用户对软件的性能的观点达成一致。
6、数据库的压力测试
说起测试,我们首先想到的就是软件正确性的测试,即常说的功能测试。软件功能正确仅是软件质量合格指标之一。在实际开发中,还有其它的非功能因素也起着决定性的因素,例如软件的响应速度。影响软件响应速度的因素有很多,有些是因为算法不够高效;还有些可能受用户并发数的影响。
在众多类型的软件测试中,压力测试正是以软件响应速度为测试目标,尤其是针对在较短时间内大量并发用户的访问时,软件的抗压能力。但压力测试往往是手工难以测试的,必须借助自动化测试工具。常用的压力测试有:Web测试、数据库测试等。
数据库在大多数软件项目中是不可缺少的,对于它进行压力测试是为了找出数据库对象是否可以有效地承受来自多个用户的并发访问。这些对象主要是:索引、触发器、存储过程和锁。通过对SQL语句和存储过程的测试,自动化的压力测试工具可以间接的反应数据库对象是否需要优化。
这些自动化的测试工具很多,各有特点,基于Java的项目可以使用JMeter,.Net项目可以采用.Net集成开发环境中提供的测试方案。
7、结束语
总之,在应用系统的测试中,把数据库应当作为独立的系统来测试,这无疑会为应用软件的质量增加可靠的保障,同时还必须结合应用软件进行集成测试,只有二者有机结合起来,才能最大限度的发挥数据库和应用软件的功能。
1、质量控制
软件质量控制对开发过程中的软件产品的质量特性进行连续的收集和反馈,通过质量管理和配置管理等机制,使软件开发过程向着既定的质量目标发展。质量控制是质量管理的的路标和动力,质量管理是质量控制的执行机制。
问题1:软件质量控制应该注意哪些方面?
建议:
(1)在整个软件生命周期中都该进行质量控制;
(2)不同阶段活动不同,应采用不同的技术;
(3)综合使用“预防性”和“检测性”技术。
问题2:软件质量控制技术有哪些类型?
建议:
(1)预防性技术:通过为过程、产品和资源设立标准等途径,来避免在产品开发过程中产生缺陷;
(2)检查性技术:用于发现和纠正缺陷,甚至分析产生缺陷的原因。
问题3:软件质量控制一般有哪些方法?
建议:
(1)目标问题度量法:通过确定软件质量目标并连续监视这些目标是否达到来控制软件质量;
(2)风险管理法:设别和控制软件开发过程中对软件质量危害最大的因素;
(3)PDCA质量控制法:PDCA是一个基于统计方法的迭代过程,已被作为国际标准。
问题4:软件质量控制的准则有哪些?
建议:
(1)制定明确的改进质量目标,满足客户需要;
(2)持续改进过程以提高质量和生产率,降低成本;
(3)消除恐惧,让员工更有效地工作;
(4)消除领域障碍,建立团队精神;
(5)不以口号要求零缺陷、高效率;
(6)进行培训,为所有人建立学习和自我提高机制。
2、质量目标
为了达到质量控制,测试团队不但需要明确软件的功能,还要明确软件应达到什么样的质量标准,即制定软件的质量目标。为了达到这些目标,在开发过程的各个阶段进行检查和评价。在质量评价时,需要有对质量进行度量的准则和方法,但更重要的是,需要在软件生存期中如何使用这些准则和方法的质量保证步骤及提高该项作业生产率的工具。
问题1:制定合理的质量目标需要从哪些方面考虑?
(1)适应性:必须制定能适应各种用户要求、软件类型和规模的质量标准,并能够度量;
(2)易学性:不需要特殊技术,软件技术人员人人都容易掌握;
(3)可靠性:对同一个软件的评价,评价的人或场合可能不同,但评价结果必须一致;
(4)针对性:不是在检查时才改进质量,而必须从设计阶段起就确立质量目标,在各个阶段实施落实;
(5)客观性:要从各种不同角度加以评价,并将评价结果定量地表示,使得人人都能理解;
(6)经济性:考虑如何才能把质量度量和保证所需要的费用控制在适当的范围内。
问题2:测试团队,需要重点关注哪些质量指标?
建议:
(1)测试设计覆盖率
(2)测试执行覆盖率
(3)各阶段缺陷密度
问题3:测试过程中,需要关注哪些测试缺陷密度?
建议:
(1)测试计划评审缺陷发现密度
(2)测试策略/方案评审缺陷发现密度
(3)测试用例评审缺陷发现密度
(4)系统测试缺陷发现密度
(5)集成测试缺陷发现密度
(6)验收测试缺陷密度
3、同行评审
在软件开发过程中邀请同行对工作产品进行审查,以图尽早查找出工作产品缺陷,进行质量控制的一种质量活动。需要前期准备、计划,安排好时间进度表,而且越早开展对项目越有价值。
问题1:常见的评审有哪些形式?
建议:
(1)审查:由公正的、接受过正式评审技术培训的组织者引导进行的同行检查;
(2)走查:又称走读,由产品的设计者或开发人员引导开发组成员和其它相关组成员浏览软件工作产品;
(3)分发:又称轮查,产品的设计者或开发人员将要评审的工作产品共享或分发,评审人员以修订标记或批注的方式将意见直接添加到工作产品或其复件上。
问题2:评审过程中常见的问题?
建议:
(1)项目进度紧张,开发人员没有时间进行评审;
(2)评审力度不够,评审发现的有效问题太少;
(3)评审会议中过多争论占用大量时间;
(4)评审专家与作者,或者多位评审专家之间的评审意见不一致;
(5)评审发现问题修改后,评审人员跟踪不充分。
问题3:项目进度紧张,专家没有时间进行评审怎么办?
建议:
(1)将评审活动的时间、需要的评审专家写入项目计划;
(2)通过“正式渠道”协调评审专家资源,并得到承诺;
(3)评审开始前提前1-2天通知评审专家。
问题4:如何可以提高测试评审的效果,达到预期的效果?
建议:
(1)评审前可以使用Checklist、代码检视工具等进行自检活动;
(2)考虑知识结构、观点角度等方面,选择合理评审专家;
(3)必要时安排介绍会议,向相关专家介绍被评审对象;
(4)充分安排好足够预审时间。
4、漏测预防
漏测是指软件产品的缺陷在某一阶段未被发现而遗漏到了后续阶段、经效果评估后,将有效的预防措施纳入到流程或相关预防平台中,制定改进措施和跟进实施。
问题1:哪些环节容易发生漏测?
建议:
(1)需求分析,如需求分析遗漏、需求分析特性理解错误、需求变更未及时跟踪;
(2)策略漏测,如组网考虑不全面、继承特性考虑不全、性能稳定性考虑不全面;
(3)设计漏测,如用例描述不规范准确、用例观察点遗漏、功能交互遗漏、异常考虑不全面等;
(4)执行漏测,如用例执行构造数据不全面、没有严格按步骤执行用例、测试技能或经验不足等。
问题2:漏测分析需要做哪些工作?
建议:
(1)选择问题,选择有代表性的漏测问题;
(2)分析根因,进行漏测问题的根因分析;
(3)改进实施,制定改进措施并跟进实施;
(4)补充测试设计,共性问题要跟踪多版本闭环;
(5)成果固化,经效果评估后,将有效的预防措施纳入到流程或相关预防平台中。
问题3:有哪些方法,可以进行漏测预防?
建议:
(1)测试策略和测试方案充分考虑各业务逻辑之间的交互和影响;
(2)测试用例设计时,充分考虑功能点与其他模块之间的交互和影响;
(3)充分考虑修改问题单、需求变更是否引入新的问题;
(4)参考优秀实践和经验案例;
(5)每次缺陷分析完要有总结,把容易漏测的形成测试经验checklist,并组织学习。
5、发散测试
发散测试,顾名思义就是不以某个标准或者框框作为约束的一种测试,发散测试准确来说应该叫具备发散思维的探索性测试。为了提高测试执行覆盖率,在严格按照用例测试执行后,通常需要进行发散测试,这里包括自由测试和交叉测试。
问题1:发散测试,需要关注哪些方面?
建议:
(1)重点模块和核心流程,需要安排多人进行交叉测试;
(2)根据2/8原则,对发现缺陷高的模块,需要重点安排人力交叉测试;
(3)了解用户场景,按照用户常常使用的实际场景进行发散测试;
(4)识别异常场景,模拟可能发生的各种异常场景进行发散测试;
(5)遵循规范原则,根据规范组织测试,如协议规范、设计规范和接口规范等。
版权声明:本文出自 yubiao584521 的51Testing软件测试博客:http://www.51testing.com/?220076
原创作品,转载时请务必以超链接形式标明本文原始出处、作者信息和本声明,否则将追究法律责任。