qileilove

blog已经转移至github,大家请访问 http://qaseven.github.io/

我的软件测试之旅:(12)机遇——测试自动化培训师和教练

  测试自动化小组尝试过另一款芬兰同事开发的新型框架,名字叫做robotframework,如今已经开源。这个框架本身使用Python语言开发完成,用来开发可接受性测试,是关键字驱动的测试自动化框架,支持多种测试用例的格式,我最喜欢的是使用表格的HTML文件格式。框架非常好用,各方评价都非常高,但是由于核心的开发者都在芬兰,杭州本地需要有人能够进行培训、辅导,才有可能做到快速地推广使用。于是测试自动化小组的同事参加了该框架的高级培训,以及如何进行入门级培训的培训,然后向杭州研发中心的其他同事提供培训,帮助他们使用这个框架实现测试用例的脚本化。

  测试框架的使用推广很快,该产品线决定全面采用此框架来实现测试自动化,包括手工测试的用例也需要使用此框架来撰写和管理。并且还专门成立了一个团队为产品线开发库函数,封装操作命令,以便于测试人员们可以不必担心太多库函数编程的问题,快速地实现测试用例的脚本化。

  但是测试自动化并不只是将测试用例实现脚本化而已,它是一个复杂的活动,同样包含测试用例的设计、测试方案的选择、测试验证手段的选择等等步骤,而且还必须考虑到在自动化执行的情况下,机器和被测对象的之间的交互方式和人不同,观察被测对象反应的方式也不同。更不用提到脚本本身也是一种代码,而代码如果质量不高就会产生大量的技术债务。在缺乏编程经验的测试人员手中就更是一场灾难,他们会复制、粘贴现有的测试脚本,再做相应的修改,重复的脚本片段或是无关脚本片段的残留物可谓是司空见惯。测试人员也渐渐地从欢迎转变到了叫苦连天的态度,抱怨他们需要花非常多的时间调试出错的脚本,根本就没有时间去进行“真正的”测试。

  为了能够更好地推广测试自动化,也为了能够解决测试自动化所带来的这些问题,管理层决定设立测试自动化教练的角色。我是被选定的两位教练之一。我们所服务的对象不仅仅是参加过Scrum试点项目的那批人,还包括刚刚进行敏捷转型不久的数百人大组织。

  在培训师和教练的工作中,我发现一些常见的问题:

  ● 不愿意花时间去熟悉工具:很多测试工作者参加完基础培训之后就不再花时间去查看工具的相关文档,不愿意去熟悉其操作技巧以及库函数开发,连一些非常简单的小技巧也不知道,开发出来的测试用例很臃肿,使用的测试操作往往要耗费很多的CPU或者内存资源以及执行时间。

  ● 不愿意去阅读现有的用例:由于种种原因,一些测试用例本身就非常难读懂,导致后续接手的测试人员不愿意花时间去理解用例的设计思路,或者是去理解其中的每一步操作,往往满足于用例运行通过即可,用例失败时也只是以修改后用例通过为目的。

  ● 需求和用例间断裂的联系:敏捷转型给测试工作带来了一系列的挑战,首当其冲的就是从需求到测试用例的整条线索如何延续。以往会比对软件模块的功能需求条目和测试用例,衡量测试的覆盖率,而Scrum模式下开发的粒度是特性,无法沿用此做法。于是在此时,我们提出了Robot Case as Requirement的想法,也即利用robotframework测试用例本身描述性强的特点,来承载需求文档的作用,而测试用例自身就是可执行的自动化脚本。

  ● 测试工作者失去的归属感:在Scrum模式下,测试工作者散布于每一个跨职能特性团队中,很可能孤单一人,团队里最多也就2~3个做测试的。相比以往单独的测试部门,大家没有了归属感,也不知道该找谁问问题、在哪里获取测试相关信息以及如何提高自身的测试能力。我们也为此成立了测试社区Test Community,定期聚会,邀请大家来分享自己的心得体会,互相学习提高,还有在线论坛供大家交流,社区已经可以自我推动着持续发展壮大。

  ● 缺乏测试自动化理论知识:测试自动化(Test Automation)和自动化测试(Automated Test)其实是两个不同的概念,测试自动化是包括测试计划、测试用例设计、执行、分析、报告各个阶段在内的一系列活动,而自动化测试可以看做是仅仅把测试用例的操作实现成脚本而已。缺乏测试自动化理论知识会累积测试技术债务,难以维护、脆弱易损的测试脚本就像是低质量、漏洞百出的代码,后期的维护、修复开销远超过鲁莽自动化所带来的那一点点效益。

  ● 软件本身低下的可测性:可测性可谓是测试自动化的关键,可测性不高的软件本身就难以进行测试,测试自动化则更加依赖软件的可测性。机器不像是人,可以通过眼睛观察多处信息,并且找到其中的联系,或是在脑中进行逻辑分析,从蛛丝马迹间找到问题。测试自动化需要采取适合于机器的脚本执行、信息收集、结果分析的测试方法,一定程度上这都依赖于软件本身要提供准确的、适量的信息。

  ● 测试工作者不熟悉Scrum:Scrum模式要求团队是跨职能特性团队,应该着手培养出通才化专家(Generalizing Specialist),通过加强团队内的交流,开发、测试以及其他职能人员通过合作和沟通互相学习,摸索出适应迭代增量模式的工作方法。但多数测试工作者依然是按照类瀑布模式的方式工作,在Sprint前期空闲后期忙碌,来不及完成测试。当然,这也和不了解测试自动化,或是可接受性测试驱动开发有关系。

相关链接:

我的软件测试之旅:(1)起点——作为软件开发人员

我的软件测试之旅:(2)转变——作为专职测试人员

我的软件测试之旅:(3)同期——加入测试自动化小组

我的软件测试之旅:(4)并行——自动化回归测试

我的软件测试之旅:(5)难点——功能改进的测试

我的软件测试之旅:(6)跳转——追逐新鲜事物的探险者

我的软件测试之旅:(7)启程——Scrum中的测试工作者

我的软件测试之旅:(8)困难——没有现成的测试工具

我的软件测试之旅:(9)行动——简化测试文档和流程

我的软件测试之旅:(10)贡献——开发项流程

我的软件测试之旅:(11)尝试——Scrum Master

posted @ 2012-08-13 09:11 顺其自然EVO 阅读(192) | 评论 (0)编辑 收藏

性能测试知多少---性能测试工具原理与架构

 在性能测试学习过程中,坚持思想与工具(分开)并行,当前面世面上的性能测试书籍大多把理论与loadrunner融为一体讲解,这样做是正确的,因为有一些性能名词概念也源于工具。但是,性能测试不是loadrunner,所有的作者也是这么认为的。但他们在讲性能测试的时候讲的就是loadrunner有,只是讲的多少不同罢啦。

  你是否觉得我对loadrunner有仇?我之所以将其分开来学,只是希望自己在学习性能测试的时候不要被loadrunner局限了而已。只是觉得在做性能测试时不要带loadrunner的思维,这样更容易把握性能测试的本质。

  性能测试工具,从广义上讲,在性能测试过程中使用到的所有工具都可以称其为性能测试工具。从狭义上来讲,我们可以把性能测试工具分为服务器端性能测试工具与前段性能测试工具。

  服务器端性能测试工具也我们测试人员通常所认为的性能测试工具。LoadRunner、JMeter、SilkPerformance、服务器端压力性能工具需要支持产生压力和负载,录制和生成脚本,设置和部署场景,产生并发用户和向系统施加持续的压力。

  前端性能测试工具应用比较广泛,开发人员,前端开发人员、测试人员都会经常用到。Firebug 、fildder2、Yslow 、前端性能测试工具只需要关于心浏览器等客户端工具对具体需要展现的页面的处理过程。

  服务器性能测试工具原理

  性能测试工具的主要作用是通过模拟生产环境中的真实业务操作,对被测试系统实行压力负载测试,监视被 测试系统在不同业务、不同压力性能下的性能表现,找出潜在的性能瓶颈进行分析、优化。

  客户端与服务器相当于两个人,通过信息来进行交流。由于初次见面不好意思直接交流,与是找来了中间传话人,客户端把信息告诉给传话人,由传话人来转达给服务器。那么服务器反馈的信息也由传话人转达给客户端。一般性能测试工具都需要录制或编写客户端行为脚本。

  这样传达人就有了客户端的行为能力,从而假扮客户端来欺骗服务器,与之进行通信。有了客户端行为了传达人可以进行自我复制。从而变出N多个传达人对服务器进通信。---这个传达人的行为和能力也就是性能测试工具的基本特质。(突然觉得性能工具像第三者插足,而且是可以自我复制疯狂变态的第三者,哈哈!)

  对于目前流行的性能测试工具,他们的基本工作原理都是一致的。在客户端通过多线程或多进程模拟虚拟用户访问,对服务器端施加压力,然后在过程中监控和收集性能数据。

  性能测试工具应该具备什么的特质呢?

  1、工具本身占用系统资源少,可扩展性好,可用性强。

  2、能模拟真实业务事务操作,在并发时能真正产生业务压力。(这一点是核心)

  3、对压力测试结果能很好地进行性能分析,快速找出被测试系统的瓶颈。

  4、测试脚本的重复性强。

  服务器性能测试工具的架构

  用户行为生成部分

  我为什么说的这么朦胧,对于熟悉loadrunner的朋友,我说成虚拟用户脚本生成器,你更容易理解,这个脚本,我们可以录制,也可以手工编写。你不要以为这是生成用户行为的唯一方式。因为在JMeter成中是添加各种组件,通过对组件的配置来完成用户行为的,当然也可以通过录制。而在相对简陋的性能测试工具curl_loader(linux环境下的运行的),他是通过编写配置文件的形式来描述用户形为的。

  我前面也有提了,虽然性能测试工具由不同的形式来描述,但他们的原理是一样的,都是通过Proxy方式来实现,具体来说,Proxy作为客户端和服务器之间的中间人,接收客户端的数据包。

  压力产生器

  压力产生器用于根据脚本内容产生实际的负载,在性能测试工具中,压力产生器扮演着“产生负载”的角色。也就根用户的设置,进行自我复制来生成多个客户端向服务器发送请求。对于工具来说,每复制出来的一份就是一个进程或线程,进程和线程的运行是要占用系统资源的。所以,对一台压力测试机来说能运行的虚拟用户数也是有限的。根基测试机的配置而定。那么这个时候就要通过多台测试机合作,来模拟更多的虚拟用户向服务器发请求。

  那么,对于性能测试来说,很重要的一点就是产生“并发”的请求,不然就不会对服务器产生压力。那多台机子如何产生“步调一致”的虚拟用户呢?使用“用户代理”

  用户代理

  用户代理是运行在负载机上的进程,该进程与产生负载压力的进程或线程协作,接收调度系统的命令,调度产生负载压力的进程或线程,从这个意义上看,用户代理也是压力产生器的一部分。

  调度能力

  我们在做复杂的性能测试时,常常会设计各种场景,不同的虚拟用户数,不同事务的用户比例,运行时间,设置同步点等,这个时候也需要我们的测试工具有压力调度能力。从而才能更真实的模拟我们所设计的运行场景。

  监控系统

  监控系统是性能测试工具直接与用户进行交互的主要部分,监控系统,主要用户在压力测试过程中对各种软硬件进行监控,如对数据库、应用服务器,服务器的主要性能表现情况进行监控。用于判断系统当前处于什么状态。

  当然,监控系统不是性能工具必须的部分,可以通过软硬件系统自身的监控工具或者第三方监控工具进行监控。但是否有强大的性能计数器监控系统是衡量性能测试工具是否强大的指标之一。

  压力结果分析

  压力结果分析工具可以用来辅助进行测试结果的分析,性能测试工具一般都能将监控系统获取的性能技术数器值生成曲线图,折线图等各种图表。通过展现性能测试过程中的各种参数指标,来供测试人员进行分析。

  但这里需要强调的是,压力结果分析工具本身不能代替分析者进行性能结果分析,而只是提供多种不同的数据揭示和呈现方法而已。对于这些数据进行分析必然要依靠测试工程师对系统性能分析的知识和经验。

  对上面介绍的性能测试工具架构的组成部分,不是第一个性能测试工具都具备,而所具备的强大程度也不相同。比如,有些性能测试工具不具备用户代理能,有些监控系统能监控的资源很有限或简陋,有些结果分析数据的呈现不够详尽等。

相关链接:

性能测试知多少----性能测试分类之我见

性能测试知多少---并发用户

性能测试知多少---吞吐量

性能测试知多少---响应时间

性能测试知多少---了解前端性能



posted @ 2012-08-10 10:34 顺其自然EVO 阅读(327) | 评论 (0)编辑 收藏

LoadRunner脚本编写之三(事务函数)

关于脚本的这块,前两篇都在讲C语言,其实,要整理点实用的东西挺难,在应用中多对录制的脚本分析,但对于新手学脚本确实无从下手。

  先贴一个脚本:

  重点代码部分:

Action()
{


    web_url("webhp",
        "URL=http://www.google.com.hk/webhp?hl=zh-CN&sourceid=cnhp",
        "Resource=0",
      .....
    lr_start_transaction("登陆");    //设置事务开始

    web_submit_data("ServiceLoginAuth",
        "Action=https://accounts.google.com/ServiceLoginAuth",
        "Method=POST",      
   ........

    web_file("web_find","defnngj@gmail.com",LAST);  //设置检查点

    lr_end_transaction("登陆",LR_AUTO);    //设置事务结束

    return 0;
}

  上面的一段代码是我录制的一个google登录的过程,详细过程描述:

  1、进入google首页

  2、点击右上角的登录链接,跳转到登录页面

  3、设置登录事务开始,输入用户名密码,点击登录

  4、登录成功后跳转后google 首页,右上角出现登录的用户名,设置事务结束。

  检查点

  在上面的操作中,我设置了一个检查点,web_fiind ,判断后我是否登录成功一个重点的标志是在google首页右上角是否出现我的用户名。所以,我根据这个特点设置检查点,来检查登录是否成功。设置检查点的函数有三个,

  web_find()函数:在页面中查找相应内容

  web_reg_find()函数:在缓存中查打相应的内容

  web_image_check()函数:在页面中查找具体图片。

  他们的具体用法,你们可以在脚本的编写中,光标定位在函数中,按F1 查看帮助文档或参考其它文档。

  关于脚本中的事务

  我们在一个脚本中可能要做很多操作,我们为了分清脚本中某一段代码具体是做什么,所以,在执行某个操作的前后需要添加事务,用来标志事务的开始与结束,这样可以使脚本更清晰。当然,对于不同的事务需要分开录制。比如,某电子商务网站的浏览商品与交易。他们的比例是不同的,对于比较复杂的场景,是多个事务按不同的比例并行的。设置80%的用户浏览商品,20%的用户进行交易。

  当然,事务与事务之间是会有依赖关系的。如果我们把访问首页定为一个事务,登录定为一个事务,浏览商品定为一个事务,交易定为一个事务。那么我们要想录制(编写)交易的事务,那么前面三个事务是先觉条件。所以,我们知道录制一个脚本的目的是哪个操作,然后在其前后添加事务标识。

  对80%的用户浏览商品,20%用户交易,交易的前提是先浏览一个商品,也就是所有用户(100%)都浏览了商品,只有20%的用户去交易。这样是合乎逻辑的。

  事务函数

  在上面的脚本中我们用到了lr_start_transaction()函数和 lr_end_transaction()函数来标识一个事务的开始与结束,除些之外,loadrunner还提供了许多与事务相关的函数,这里介绍几个常用的。

  1、lr_set_transaction_instance_status 用于设置事务的状态,事务的状态包括:LR_PASS、 LR_FAIL 、  LR_AUTO 、  LR_STOP  。可以在脚本中根据条件设置事务的状态,例如,根据检查点返回的结果来设置事务为通过还是失败。

if(event == GENERAL_ERROR)
        lr_set_transaction_instance_status(LR_FAIL);
    lr_end_transaction("登陆",LR_AUTO);

  2、lr_fail_trans_with_error与lr_set_transaction_instance_status 类似,都可以用于设置事务的状态,区别在于lr_fail_trans_with_error除了可以设置的状态,还可以输出错误日志信息。

if(status != SUCCESS)
        lr_fail_trans_with_error("an error has occurred:%s",my_get_error_string(status));
    lr_end_transaction("登陆成功",LR_AUTO);

  3、lr_get_transaction_status与前两个函数的作用相反,用于获取事务的状态。

if (lr_get_transaction_status() == LR_FAIL)
    {
        //由于web_url请求失败了,所以没有必要继续执行下去,因些设置事务状态为FAIL
        lr_end_transaction("登陆失败",LR_FAIL);
        return;
    }

  4、lr_get_transaction_duration 用于获取事务所消耗的时间。这个就比较有意思了。

  我们登录百度首页,插入一个事务,然后访问百度注册页面。下面计算访问注册页面的时间。

  完整脚本:

Action()
{
    double trans_time;  //定义变量

    web_url("www.baidu.com", 
        "URL=http://www.baidu.com/", 
        "Resource=0", 
        "RecContentType=text/html", 
        "Referer=", 
        "Snapshot=t14.inf", 
        "Mode=HTML", 
        EXTRARES, 
        "Url=http://s1.bdstatic.com/r/www/cache/aoyun/img/i-1.0.1.png", ENDITEM, 
        "Url=/favicon.ico", "Referer=", ENDITEM, 
        "Url=http://s1.bdstatic.com/r/www/img/bg-1.0.0.gif", ENDITEM, 
        "Url=http://nsclick.baidu.com/v.gif?pid=201&pj=www&rsv_sid=1289_1328_1262_1228_1344_1342_1186_1281_1178_1287_1320_1294_1330&fm=behs&tab=tj_reg&un=&path=http%3A%2F%2Fwww.baidu.com%2F&t=1343538345708", ENDITEM, 
        "Url=https://passport.baidu.com/favicon.ico", "Referer=", ENDITEM, 
        LAST);

    lr_start_transaction("访问注册页");  //定义事务开始

    web_link("???", 
        "Text=???", 
        "Ordinal=2", 
        "Snapshot=t15.inf", 
        EXTRARES, 
        "Url=../img/breadbg.gif", "Referer=https://passport.baidu.com/v2/?reg&regType=1&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F", ENDITEM, 
        "Url=../js/pass_api_reg.js?v=20120711", "Referer=https://passport.baidu.com/v2/?reg&regType=1&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F", ENDITEM, 
        "Url=../img/v2/regbtn-split.gif", "Referer=https://passport.baidu.com/v2/?reg&regType=1&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F", ENDITEM, 
        "Url=../cgi-bin/genimage?0013435383780156BF1F30CF18D2332EA927154DCDAB3B6B40F9E25197273F556454857E2FADF7BA23531BE59EEDE0EF92F2F006F8D595B88A907E318D2A249CBAB109FCDB3AB38ED4453F3BC149C6A5FD6240B97D598BA84EE3CEEE3F4359D3469309D88EE55C19B04251D2212171720B476D0A0D6277787DD43D9BA29E426A82BFD90E248FA15A32F1838B3E15D63B8CFE4E3DC6EAD4F23FE0DB457E5AE6B82DACCB79EE9EF289", "Referer=https://passport.baidu.com/v2/?reg&regType=1&tpl=mn&u=http%3A%2F%2Fwww.baidu.com%2F", ENDITEM, 
        "Url=../img/v2/reg_input_bg.gif", "Referer=https://passport.baidu.com/v2/?reg&regType=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;
}




  重点代码部分:

Action()
{
    double trans_time;  //定义变量

    web_url("www.baidu.com", 
        "URL=http://www.baidu.com/", 
       .....

    lr_start_transaction("访问注册页");  //定义事务开始

    web_link("???", 
        "Text=???", 
        "Ordinal=2", 
        "Snapshot=t15.inf", 
     ......

    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;
}

  运行结果:

.......
Action.c(39): The duration up to the submit is 0.029588 seconds
Action.c(45): Notify: Transaction "访问注册页" ended with "Pass" status (Duration: 5.0300 Wasted Time: 0.0296).
Ending action Action.
Ending iteration 1.

  39行:持续时间长达0.029588秒的提交

  45行:通知:交易”访问注册页”结束,“通过”状态(持续时间:5.0300 浪费时间:0.0296)。

  从上面的结果中发现,我们设置lr_get_transaction_wasted_time函数的时间0.029588 与事务结束浪费的时间 0.0296 非常接近。 这是因为我把 lr_get_transaction_wasted_time函数插入在了事务结果的前面,其实,我们可以将lr_get_transaction_wasted_time插入在事务执行过程的任意位置来计算消耗时间。

相关链接:

LoadRunner脚本编写之一

LoadRunner脚本编写之二


posted @ 2012-08-10 10:21 顺其自然EVO 阅读(583) | 评论 (0)编辑 收藏

MySQL内部临时表策略

  通过对MySQL数据库源码的跟踪和调试,以及参考MySQL官方文档,对MySQL内部临时表使用策略进行整理,以便于更加深入的理解。

  使用内部临时表条件

  MySQL内部临时表的使用有一定的策略,从源码中关于SQL查询是否需要内部临时表。可以总结如下:

  1、DISTINCT查询,但是简单的DISTINCT查询,比如对primary key、unique key等DISTINCT查询时,查询优化器会将DISTINCT条件优化,去除DISTINCT条件,也不会创建临时表;

  2、不是第一个表的字段使用ORDER BY 或者GROUP BY;

  3、ORDER BY和GROUP BY使用不同的顺序;

  4、用户需要缓存结果;

  5、ROLLUP查询。

  源码如下所示

  代码地址:sql_select.cc:854,函数:JOIN::optimize(),位置:sql_select.cc:1399

/*
    Check if we need to create a temporary table.
    This has to be done if all tables are not already read (const tables)
    and one of the following conditions holds:
    - We are using DISTINCT (simple distinct's are already optimized away)
    - We are using an ORDER BY or GROUP BY on fields not in the first table
    - We are using different ORDER BY and GROUP BY orders
    - The user wants us to buffer the result.
    When the WITH ROLLUP modifier is present, we cannot skip temporary table
    creation for the DISTINCT clause just because there are only const tables.
  */
  need_tmp= (( const_tables != tables &&
               (( select_distinct || !simple_order || !simple_group) ||
                ( group_list && order ) ||
                test(select_options & OPTION_BUFFER_RESULT))) ||
             ( rollup.state != ROLLUP:: STATE_NONE && select_distinct ));

  内部临时表使用原则

  但是使用了内部临时表,那么他是怎么存储的呢?原则是这样的:

  1、当查询结果较小的情况下,使用heap存储引擎进行存储。也就是说在内存中存储查询结果。

  2、当查询结果较大的情况下,使用myisam存储引擎进行存储。

  3、当查询结果最初较小,但是不断增大的情况下,将会有从heap存储引擎转化为myisam存储引擎存储查询结果。

  什么情况算是查询结果较小呢?从源码中if的几个参数可以看出:

  1、有blob字段的情况;

  2、使用唯一限制的情况;

  3、当前表定义为大表的情况;

  4、查询结果的选项为小结果集的情况;

  5、查询结果的选项为强制使用myisam的情况

  源码如下所示

  代码地址:sql_select.cc:10229,函数:create_tmp_table(),位置:sql_select.cc:10557

/* If result table is small; use a heap */
  /* future: storage engine selection can be made dynamic? */
  if ( blob_count || using_unique_constraint
      || ( thd->variables .big_tables && !( select_options & SELECT_SMALL_RESULT ))
      || ( select_options & TMP_TABLE_FORCE_MYISAM ))
  {
    share->db_plugin = ha_lock_engine(0, myisam_hton);
    table->file = get_new_handler( share, &table ->mem_root,
                                 share->db_type ());
    if (group &&
          ( param->group_parts > table-> file->max_key_parts () ||
           param->group_length > table-> file->max_key_length ()))
      using_unique_constraint=1;
  }
  else
  {
    share->db_plugin = ha_lock_engine(0, heap_hton);
    table->file = get_new_handler( share, &table ->mem_root,
                                 share->db_type ());
  }

  代码地址:sql_select.cc:11224,函数:create_myisam_from_heap(),位置:sql_select.cc:11287

/*
    copy all old rows from heap table to MyISAM table
    This is the only code that uses record[1] to read/write but this
    is safe as this is a temporary MyISAM table without timestamp/autoincrement
    or partitioning.
  */
  while (! table->file ->rnd_next( new_table.record [1]))
  {
    write_err= new_table .file-> ha_write_row(new_table .record[1]);
    DBUG_EXECUTE_IF("raise_error" , write_err= HA_ERR_FOUND_DUPP_KEY ;);
    if (write_err )
      goto err ;
  }

  官方文档相关内容

  以上内容只是源码表面的问题,通过查询MySQL的官方文档,得到了更为权威的官方信息。

  临时表创建的条件:

  1、如果order by条件和group by的条件不一样,或者order by或group by的不是join队列中的第一个表的字段。

  2、DISTINCT联合order by条件的查询。

  3、如果使用了SQL_SMALL_RESULT选项,MySQL使用memory临时表,否则,查询询结果需要存储到磁盘。

  临时表不使用内存表的原则:

  1、表中有BLOB或TEXT类型。

  2、group by或distinct条件中的字段大于512个字节。

  3、如果使用了UNION或UNION ALL,任何查询列表中的字段大于512个字节。

  此外,使用内存表最大为tmp_table_size和max_heap_table_size的最小值。如果超过该值,转化为myisam存储引擎存储到磁盘。


posted @ 2012-08-10 09:55 顺其自然EVO 阅读(882) | 评论 (0)编辑 收藏

约束与数据库对象规则、默认值的探究

  约束、规则、默认值这三者在数据表中规定了数据进行操作时的限制条件。他们三者有关系也有区别,用了两天的时间研究了他们三者,总结如下。

  首先让我们从关系角度来分析:

  ● 浅谈约束、规则、默认值

  1、约束,约束顾名思义是限制条件的意思,其实它的作用也是如此,它是对所要进行增删改查操作的数据进行一次检查,检查这些数据是否符合我们所要约束的条件。举个例子:我们都经历过高考,考试规定考生不许带与考试无关的物品这就是一种约束。报名考试的考生可能因为某些意外没有进入考场考试,但大部分考生还是进行了考试。那么在考场内就产生了两种情况空位和非空位,这就是我们约束条件的两种情况。我们继续分析,在考生进入考场的情况中,考生在答卷之前是必须要在试卷和答题卡上填写自己的信息的,这些信息都是必须要填的因为它们唯一确定了一个考生,这里考生必须填写考生信息也就是我们所说的主键约束(主关键字约束)。接下来我们用两个表来说明下外键约束:

  那么如果我们在字段后面用Not Null规定了呢?也就是说在我们的考生信息表中的性别字段,该列是不允许为空的于是就用Not Null标识,但是如果我们不填的话呢,我们的系统就会用Default默认值约束来帮助我们天上一个值。接下来我们来看Check约束,该约束是要用条件表达式来限定所要填的数据的,如下:

         Create table 工资 (

                  name  char(10) primary key,

                  department  char(20) Not Null,

                  salary int not null check(salary>1000 and salary<4000)

         )

  创建了一个表该表中有三个字段,name、department、salary,并在salary字段设置了约束条件。该表如下图:




 在表中填数据时后两个不符合条件是不正确的。

  在约束中还分为两种:字段级约束和表级约束。字段级约束是只约束其中的一列,表级约束是约束多列,它们两个的区别并不大,我们在理解时可以将表级理解为字段级,即:被约束的多列可以看做捆绑成一列,被捆绑后的字段中的两个记录是不能重复的,如:

  2、规则,规则顾名思义是规矩制度的意思,在进行表操作时它的作用和check约束条件是类似的,但是一个表中只有一个规则,可有多个check约束,如:

     Create table 薪水(

            name  char(10) primary key,

            position  char(20) Not Null,

            salary int not null

         )

     Create rule ru_salary

     As @salary>1000 and @salary<4000

     Go

     Exec sp_bindrule ‘ru_salary’,  ’薪水.salary’

       Go

  表说明如下:




  3、默认值,用法同规则,它的作用类似于Default约束,是说在表中插入数据时,如果没有指定值,默认值自动指定数据值。

  接下来我们在语法上分析三者,看图:

  1、约束,创建方法大致有三种:

  ——方法一:在创建表时用字句的方式创建约束

 CREATE TABLE 表名(

            列名数据类型,

            ……

           <SPAN style="COLOR: #009900"> CONSTRAINT 约束名 PRIMARY KEY (列名),                       ——主键约束

            CONSTRAINT 约束名 UNIQUE (列名),                            ——唯一约束

            CONSTRAINT 约束名 FOREIGN KEY (列名) REFERENCES 表名 (列名),  ——外键约束

            CONSTRAINT 约束名 CHECK (检查条件)                           ——Check约束

</SPAN> );

        ——方法二:在创建表时直接定义约束

 CREATE TABLE 表名(

 <SPAN style="COLOR: #009900">           列名数据类型 NOT NULL,                                        ——非空约束

            列名数据类型 NOT NULL PRIMARY KEY,                            ——主键约束

            列名数据类型 NOT NULL UNIQUE,                                 ——唯一约束

            列名数据类型 [NOT NULL] REFERENCES 表名(列名),                ——外键约束

            列名数据类型 [NOT NULL] CHECK (检查条件)                         ——Check约束</SPAN>

 )

       ——方法三:在创建表后,通过更改表来定义

<SPAN style="COLOR: #009900"> ALTER TABLE 表名 ALTER COLUMN 列名 SET NOT NULL;

 ALTER TABLE 表名 ADD PRIMARY KEY (列名1,列名2…);

 ALTER TABLE 表名 ADD UNIQUE (列名1,列名2,……);

 ALTER TABLE 表名 ADD FOREIGN KEY(列名) REFERENCES 表名2 (列名2);

 ALTER TABLE 表名 ADD CHECK (检查条件);
</SPAN>

  删除约束的语法为:ALTER TABLE 表名 DROP CONSTRAINT 约束名

  2、规则。规则的应用操作,首先要创建但只创建是不能应用的要用sp_bindrule将规则绑定到字段或自定义的数据类型上才能起作用。

   ——创建

 <SPAN style="COLOR: #009900">   Create rule 规则名 as 条件表达式
</SPAN>
    ——绑定

   <SPAN style="COLOR: #009900"> Exec sp_bindrule ‘规则名’  , ‘表名.字段名’ | ‘数据类型’

</SPAN>    ——解除绑定

  <SPAN style="COLOR: #009900">  sp_unbindrule  ‘表名.字段名’ | ‘数据类型’
</SPAN>
    ——删除规则

    <SPAN style="COLOR: #009900">DROP RULE {rule_name} [,...n]
</SPAN>

  3、默认值的语法和规则是类似的,只需将规则中的rule改成关键字default即可。

  三者间的关系很清楚了吧。


posted @ 2012-08-10 09:36 顺其自然EVO 阅读(194) | 评论 (0)编辑 收藏

我的软件测试之旅:(10)贡献——开发项流程

  开发项流程(Development Item Process)

  当时的这个Scrum试点项目身负重任,其中之一就是要探索出在新型的敏捷模式下该使用何种的开 发流程,负责人就是当时的Linux部门经理,而我则捞到了负责测试部分流程的机会。整个试点项目的人员扩张速度不错,4个人的团队维持了好几个迭代,陆续有人加入,新的测试人员在大概是第四个迭代的时候才补充进来,而后逐渐扩张到两三个团队。这样的扩张速度对测试流程的确定来说非常好,一开始我可以只考虑自己的想法,不断地尝试摸索,可以很快地得到反馈然后改进;等到想法大致成形的时候,又可以专注于帮助其他成员理解流程和使用,验证流程的易用性;等到人员更多的时候,就可以着重验证流程的推广复用性。

  试点项目并非是全权负责新产品的开发,它其实是归属一个更大的项目、产品的,产品经理在芬兰,芬兰也还有一些团队,两地之间的团队必须要合作,虽然杭州的项目享有流程等各方面的自由,但也必须考虑和芬兰团队现有模式流程协作的问题。流程中也要把这些细节都考虑进去。

  我讨厌浪费,讨厌重复的信息,也不喜欢把不同特点的信息混淆在一起,而且流程要为人服务,它需要根据人在工作中的行为、活动特点来制定,而不是凭空想象,这是我在流程总结中所秉持的重要原则。因此,在流程中测试活动开展所需要的信息,每一份信息只应该存在于一个位置,其他地方全部应该通过链接或者引用使用这些信息,而且测试和开发都会用到的信息也适用此原则。信息应该分为长期存在和短期存在两种,可以看做是从读、写的角度进行区分:同一份信息和被测对象相关,且在可预见的未来还会继续被读写的话,看做是长期的类型;同一份信息主要是阶段性的,和特定的版本、时间点相关的,且在可预见的未来只会被读取但不会被更新(写)的,看做是短期的类型。两类信息或者以不同的文档进行维护,或者以不同的方式进行维护。

  如下简单表述一下当时所设计出来的流程,这个流程因为种种原因在试点项目结束后没有被延续使用,但是大概是三四年后我已经成为敏捷教练的时候,意外得知它居然一直在别的产品线沿用至今(当时),其生命力可见一斑。我将侧重描述其变化、改进之处,和以往流程相同的地方则不做介绍。

  ● 新流程的目标包括:推动开发和测试专家们的密切合作以提高软件的质量;合理化以及简化相关文档;减少文档数量,加强维护,以提高文档的质量;促进开发和测试人员之间的互相学习,以增加项目资源的灵活性;等等。

  ● 开发项是新提出的概念,将软件的规格说明书撰写、设计、实现和测试封装在一起,作为最小的原子化产品组件(Component)。原子化的意思是保持开发项之间的互相依赖在可以做到的最低水平;移除或重排任何开发项的时候,对其他开发项不产生(或产生最小的)影响。

  ● 在迭代开始前,先有技术报告或者需求文档,由此而产生出开发项;然后是和以往的项目过程一样的入口阶段,确定项目日程并且生成相关的高阶(High Level)文档。包括集成计划文档、项目计划文档、模块(Module)测试策略以及开发项测试计划文档都在此时创建。

  ● 所有和开发项相关的测试活动都在Sprint内完成,这些测试被称之为DIT(开发项测试),测试用例本身还是属于以往的功能测试级别。但是开发项的测试计划、测试执行、报告等一系列过程全部都要在一个Sprint中完成,测试用例的自动化比例并未做硬性规定,但当时我们的成果是100%自动化。

  ● 项目成员主要分为开发和测试两类工程师,但是角色的定义并不是拿来当做不可逾越的红线使用,必要的情况下,开发工程师也会承担部分测试任务甚至整个人投入测试,或者测试工程师也会和开发工程师一起,结对开发代码。

  ● 开发人员的工作安排会受到测试工作的影响,每日站会或者平时工作中,可能会发现软件不容易测试,就需要开发人员协助检查以及修改代码提高软件的可测性。或者是在开始写测试脚本之前,就去跟开发人员约定程序输出的内容和格式。

  ● 测试文档根据信息的长期性、短期性进行了区分。

  1、测试计划与报告:将这两个单独的文档合并到一起,在单独的章节里展示各自的信息,每个软件发布使用一份测试计划和报告。总共四个章节:被测功能描述以及模块列表(持续更新)、持续集成测试状态(每个迭代的测试报告)、总结(质量评估、经验反馈、推荐和建议)、现存问题(尚未解决或仍不明晰的问题)。目的是在单个软件发布周期内持续记录测试的状态,缩减不必要的文档量。

  2、测试用例与缺陷:每一个模块或技术领域使用一份测试用例及缺陷文档。文档内容包括:该模块或技术领域的整体描述,测试用例列表及状态,缺陷列表,测试辅助程序,操作命令。目的是提供一份可以全面了解被测模块或技术领域的文档,包括当前的所有功能、曾有的和现存的缺陷,以及如何使用操作命令和测试辅助程序进行测试。

  3、测试用例清单:用Excel记录所有的测试用例即可,信息来自于现有的测试管理系统,包括测试用例的编号、已测过的最新软件发布、已测过的最新版本信息、测试用例的版本、测试用例名称、自动化的状态。

  4、缺陷清单:用Excel记录所有的缺陷即可,信息来自于现有的缺陷追踪系统,包括缺陷的编号、标题、严重程度、缺陷单状态、相关的测试用例以及版本。目的是提供一目了然的缺陷清单,可以知晓其历史及现状。

  5、Sprint缺陷清单:记录在Sprint开发过程中发现的软件缺陷,相当于轻量级的缺陷追踪系统,无法当天修复的问题才会被记录下来,而无法在当前Sprint中解决的问题则会被录入缺陷追踪系统,并且录入前一个缺陷清单。

  Linux编程培训

  为了帮助新人快速地融入项目,我们还承担着开发一套培训课程的任务。在Linux环境下进行开发的同时,我们需要总结经验,有针对性地记录所需要掌握的各方面知识,并且做成培训材料,提供给加入团队、项目的新手。我也参与其中有少量的贡献。

相关链接:

我的软件测试之旅:(1)起点——作为软件开发人员

我的软件测试之旅:(2)转变——作为专职测试人员

我的软件测试之旅:(3)同期——加入测试自动化小组

我的软件测试之旅:(4)并行——自动化回归测试

我的软件测试之旅:(5)难点——功能改进的测试

我的软件测试之旅:(6)跳转——追逐新鲜事物的探险者

我的软件测试之旅:(7)启程——Scrum中的测试工作者

我的软件测试之旅:(8)困难——没有现成的测试工具

我的软件测试之旅:(9)行动——简化测试文档和流程

posted @ 2012-08-09 10:10 顺其自然EVO 阅读(250) | 评论 (0)编辑 收藏

负载测试计划的五点建议

  你可以通过很多可用的工具来对一个应用软件进行测试并可以得到有关性能级别和临界点等方面的报告。像大多数工具一样,你要先退出你所运行的东西并对测试做彻底的准备。我将讲到在执行正式负载测试之前要考虑的五点内容。

  调整再调整

  最近与我共事的测试顾问强调说他遇到的最大的问题是客户不了解功能性测试和负载测试之间的区别。我向他们解释了实际上我们完成了功能性测试并准备好开始进行负载测试。顾问所使用的工具中的一个小型演示表明我们还没有做好对多用户进行测试的准备。

  在功能性测试中,只是专注在功能的使用上或是功能的完整性上而不是注重个别的性能表现,这是很容易做到的。在你可以进行测试之前,每一个单一用户都必须被优化。我们的问题是要从单一用户的角度而不是同时共存的用户来进行测试。

  调整是一个重复性的处理,但是在搜集资源和长时间的负载测试之前做出彻底的工作将防止你完成过多的循环。

  关于性能目标的一致意见

  当你开始准备对商业活动的不同范围的代表进行测试规划时,你可能会对每个人关于性能方面的看法和商业情况已经被预知所感到惊讶。你将要对响应时间,每秒的采样数,可接受的错误级别和预期的同时用户的数目等问题进行讨论。

  而这可能会搞乱非技术性的管理,不要束缚在数字之上,完成负载测试中性能表现方面的目标才是重要的。这包括:测试支持同时用户的能力,对性能的监控并设定可以用来量测级别的基准。

  对负载测试量度很重要的一个理念就是虚拟用户。虚拟用户是在应用软件上模拟活动的测试工具用户。取决于你的负载工具和脚本设置,对虚拟用户和实际用户设定一个等值并不总是很容易的。而且在一个模拟中,100个虚拟用户可能会执行一个100个真实用户不可以或不可能执行的活动。

  一些工具可以让你设置一个延迟时间来代表一个用户在点击或创建一个变换屏幕的事件之前,用户在每一个屏上所花的时间。或者,你可以不要任何延迟地执行脚本来代表可以比一组精神百倍的程序员更快地进行处理工作的超级用户。

  稳定化并准备环境

  你要确定你在执行负载测试时,处于一个运行环境,硬件和软件属性在测试期间都不会改变的标准之下。设置执行标准要求对产生数字的因素进行控制,他使得变换控制处理更加地重要。

  除了对变换的控制,你必须确定运行环境对要负载内容做好了准备。这似乎是很显然的,但负载测试即使是在正常情况下也可能达到预期之外的容量。对于我们来说,这意味着对互联网更高带宽的使用,超过规划容量的活动登陆到我们的数据库。所有负责应用软件部件的人都应注意在测试中产生的负载容量。

  从所有的开发组收集所有权

  负载测试,作为一个在不同的商业领域中实现的独立任务的整合性的测试,将要求来自各个方面的合作来确定问题所在。你还需要有一些政治头脑来保证各个方面不会产生指责,对其他小组产生敌意,或是更糟的,根本对负载测试不予理睬。

  应用软件在建构时,商业活动的每一个方面都将在完成其独立任务的问题上得到关注,这里面有诸如编码商业逻辑,设计HTML和XSLT或是建构防火墙和其他验证部件等的网络基础架构等方面的内容。要保持测试的进行,每一部分都要有做调试和实现变换工作的代表。如果这些部分中的一个不遵守它的职责,测试就是停止或是失败。

  对反复测试处理的准备

  在某种程度上说,你永远不可能完成负载测试。由于互联网应用软件和硬件的动态特性,许多因素不可避免的会导致性能的不断的变换。在负载测试规划的早期,逐步地确定测试可以进行重复且基准被记录,因而你可以把结果与先前的测试结果进行对比。

  你还需要在测试失败时定义程序或是策略。计划包括问题是如何升级的,如何解决的,如何与用户沟通的。他还要包括一旦瓶颈问题得到解决时用来停止测试并恢复的可能性。在负载测试初期的基础工作将使团队获得不断的成功。

posted @ 2012-08-09 10:08 顺其自然EVO 阅读(367) | 评论 (0)编辑 收藏

项目的质量控制与质量保证的区别与联系

  项目的质量控制与质量保证存在以下几点区别与联系:

  1、质量计划是质量控制和质量保证的共同依据。

  2、达到质量要求是质量控制和质量保证的共同目的。

  3、质量保证的输出是下一阶段质量控制的输入

  4、一定时间内质量控制的结果也是质量保证的质量审计对象,质量保证的成果又可以指导下一阶段的质量工作,包括质量控制和质量改进

  5、质量保证一般是每隔一定时间如阶段末进行的,主要通过系统的质量审计来保证项目的质量(或质量保证是按质量管理计划正确的去做)

  6、质量控制是实时监控项目的具体结果,以判断他们是否符合项目的相关标准,制定有效方案,以消除产生质量问题的原因(或质量控制检查是否做的正确并进行纠正)

  QA的英文为:Quality Assurance 我们翻译为“质量保证”;

  QC的英文为:Quality Control 我们翻译为“质量控制”

  我们将这两个角色之间进行一下职责划分,以方便我们后续的讨论。

  QA:监控公司质量保证体系的运行状况,审计项目的实际执行情况和公司规范之间的差异,并出具改进建议和统计分析报告,对公司的质量保证体系的质量负责。

  QC:对每一个阶段或者关键点的产出物(工件)进行检测,评估产出物是否符合预计的质量要求,对产出物的质量负责。

  通过上面的职责划分,我们发现,如果我们将软件的生产比喻成一条产品加工生产线的话,那QA只负责生产线本身的质量保证,而不管生产线中单个产品的实际质量情况。QA通过保证生产线的质量来间接保证软件产品的质量。而QC不管生产线本身的质量,而只关注生产线中生产的产品在每一个阶段的质量是否符合预期的要求,如果我们生产的是杯子,那QC只关注:生产的材料是否是预期的,每个杯子瓶口的直径是否符合要求,杯子把手是否符合设计要求等等具体的、可量化的点。

  针对软件企业的软件开发过程而言:

  QA可以进一步明确为SQA,即:软件质量保证,只负责软件开发流程的质量,企业内相对应的角色为:软件质量保证人员,有的企业就直接称之为SQA。

  QC可以进一步明确为SQC,即:软件质量控制,只负责软件开发过程中各个阶段产出的工件的质量,产出的工件可能是相关的文档或者代码等,企业内相对应的角色为:软件测试人员。

  由于各个企业采用的开发流程和测试流程 不一样,在各个阶段SQC的对应人员不一定都为测试人员 ,如在需求阶段,产生的工件为《需求规格说明书》,对该文档的主要质量控制手段为评审,这时候在此阶段担任SQC职责的就是评审小组的成员。

  QA:主要是事先的质量保证类活动,以预防为主,期望降低错误的发生几率。是针对项目实施过程的管理手段(过程)

  QC:主要是事后的质量检验类活动为主,默认错误是允许的,期望发现并选出错误。是针对项目产品的技术手段(结果)

  打个不恰当的比方, QC是警察,QA是法官,QC只要把违反法律的抓起来就可以了,并不能防止别人犯罪和给别人最终定罪,而法官就是制订法律来预防犯罪,依据法律宣判处置结果。

  典型QA的职责:

  1、导师的角色-在项目前期,QA辅导项目经理制定项目计划,根据质量标准过程裁剪得到项目过程,帮助项目进行估算,设定质量目标,对项目成员进行过程和规范的培训以及在过程中进行指导;

  2、警察的角色-在项目过程中,QA有选择地参加项目的技术评审,定期对项目的工作产品和过程进行评审和审计;

  3、医生的角色-在项目过程中,QA可以承担收集、统计、分析度量数据的工作,用于支持管理决策;

posted @ 2012-08-09 10:03 顺其自然EVO 阅读(4482) | 评论 (0)编辑 收藏

JMeter实现load distribution


        JMeter是一个优秀的负载测试软件,作为一款开源测试工具,从功能上来说,JMeter并不比昂贵的LoadRunner工具缺少太多功能。只是JMeter在UI方面的粗糙和过于灵活的扩展性,导致很多人对其失去信心。
        LoadRunner和其他的商业负载测试工具都有一个“设置用户场景”的功能,该功能允许用户指定一个场景,在该场景中,让指定的用户负载按照一定比例进行分配,例如,我们可以设置一个场景,该场景包括三个测试脚本A,B,C,在场景中可以指定场景的总虚拟用户数为100,其中30个用户(30%)运行A脚本,20个用户(20%)运行B脚本,而50个用户(50%)运行C脚本,这种场景的方式可以很好的模拟真实用户的负载,起到较好的测试效果。
        而JMeter采用的是和大部分商业负载测试工具不同的树状结构来组织负载测试,通过线程组(thread group)、Sampler,以及控制器(Controller)来控制负载测试的进行,于是很多初次使用JMeter的朋友就会认为,JMeter无法支持上文提到的“用户场景”,转而放弃使用该工具。
        其实,JMeter虽然只提供了线程组、sampler、控制器等看似简单的元件(element),但通过这些元件的组合,JMeter可以实现相当强大的功能。接下来,我们就来看看如何通过这些元件,在JMeter中实现和LoadRunner等商业负载测试工具相同的“用户场景”功能。
        说明:以下内容建立在对JMeter的一定了解的基础上,如果不熟悉JMeter的话,不妨先google一下JMeter相关的内容,当然,最好的了解JMeter的方法是通过JMeter自己网站上的手册(英文内容)。
        在JMeter中实现“用户场景”的最主要的元件是“SwitchController”和“Beanshell Sampler”。“Beanshell Sampler”是JMeter中比较灵活的一个Sampler,和普通的Sampler不同,该Sampler并不是简单的发送一个指定协议的数据包,而是可以通过beanshell脚本语言来控制该Sampler的行为(请至http://www。beanshell。org/查看beanshell相关的信息,简单来说,Beanshell是一个基于Java的交互式脚本语言,可以很好的内嵌在其他软件中。Beanshell的语法很类似Java,但比Java更简单和简洁)。需要注意的是,由于license问题,JMeter的binary并没有包含Beanshell解释器的binary,用户需要自行从http://www。beanshell。org/上下载beanshell的解释器(一个jar包)并拷贝到JMeter安装目录的lib目录下。
        而“Switch Controller”则是JMeter中的一个简单的控制器,该控制器的作用仅仅是根据给定的参数的值决定执行该控制器下的哪一个分支。例如,在JMeter中已经设定了如下测试计划:



        注意Switch Controller的“Switch Value”的取值,当该值为0时,线程组在执行到SwitchController之后,接下去执行该节点下的第一个子项,也就是Google。cn这个Sampler;如果该值为1,显然被执行的就是Sina。com。cn这个子项。假设在线程组执行的过程中,每次执行到Switch Controller的时候,我们可以控制SwitchValue的值,则自然就可以控制该Controller下的Sampler的执行次数,也就可以实现我们前文描述的“用户场景”了。
        那么,究竟如何来控制Switch Value呢?JMeter本身是支持变量的,因此,我们可以将Switch Value指定为一个变量,然后使用一段脚本代码来修改该变量的值即可。
         前文我们已经讲到了Beanshell Sampler,我们可以使用该Sampler来产生不同的Switch Value。例如,以下的这段Beanshell代码就可以以一定的概率分别产生0-2之间的整数值:
  1. int rd = Math.random() * 10;

  2. if (rd < 6) 
  3.    vars.put("RAND", "0");
  4. else if(rd < 8)
  5.    vars.put("RAND", "1");
复制代码
        从代码中可以看到,该段代码使用了Java的random函数产生一个[0, 10)之间的整数,当产生的整数位于[0, 6)时给变量$RAND赋值为0;当产生的整数位于[6, 8)时给变量$RAND赋值为1;否则赋值为2。因为random函数本身的随机性可以得到保证,因此很容易计算得出,这段代码将变量$RAND设置为0的概率是60%,设置为1的概率是20%,设置为2的概率也是20%。
以下就是使用JMeter实现用户场景的完整测试计划(Test Plan):




        接下来我们在该Switch Controller下给定三个JavaRequest Sampler来测试一下,看看这个测试计划是否能够按照我们的预期方式执行(三个Java Request Sampler的Label分别是“0”,“1”,“2”)。我们在线程组中指定10个线程,每个线程执行100次:




执行结果如下:



        从结果中可以看出,$RAND函数被置为0,1,2的次数的确是60%,20%和20%(肯定会有一定的误差,不可能100%精确)。
后记
        JMeter作为当前应用最广泛的负载测试工具之一,其灵活性和可扩充性是最大的优势,商业负载测试工具能够实现的功能,90%以上JMeter都可以实现。而且,JMeter还可以通过简单的方式扩充Sampler来支持各种不同的协议。

posted @ 2012-08-08 14:00 顺其自然EVO 阅读(1084) | 评论 (0)编辑 收藏

MySQL 在读取异常错误缓冲区方面的提升

  先来解释下什么是错误缓冲区?

  在MySQL里面,错误缓冲区只记录最近一次出现的错误,只要是有新的错误产生,旧的就会被覆盖掉。所以想知道产生了什么错误,就得在每个有可能发生错误的语句后面紧跟着"show warnings" 或者 "show errors"。 最简单的方法是把输出的结果重定向到自己定义的日志文件里面,这样就可以方便后续查看。当然如果想随时查看这些错误,我们就得通过MySQL提供的API来实现。

  自从MySQL 5.6 这个革命性的版本出现后,问题得到了初步解决。虽然离我们想的还差好多。

  MySQL 5.6 提供了 get diagnostic 语句来获取错误缓冲区的内容,然后把这些内容输出到不同范围域的变量里,以便我们后续灵活操作。

  下来,我们就来看一个例子。

  针对的表结构为:

CREATE TABLE `t_datetime` (
  `id` int(11) NOT NULL,
  `log_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `end_time` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

  存储过程代码为:

DELIMITER $$


USE `new_t`$$


DROP PROCEDURE IF EXISTS `sp_do_update`$$


CREATEDEFINER=`root`@`localhost` PROCEDURE `sp_do_update`(
    IN f_id INT,
    IN f_log_time VARCHAR(255),
    IN f_end_time VARCHAR(255)
    )
BEGIN
      DECLARE i_con1 TINYINT DEFAULT 0;
      DECLARE i_code CHAR(5) DEFAULT '00000';
      DECLARE i_msg TEXT;
      DECLARE i_rows INT;
      DECLARE i_con1 CONDITION FOR 1048; -- 这个错误代码代表字段限制不能NULL。
      DECLARE CONTINUE HANDLER FOR i_con1
      BEGIN
        SET i_con1 = 1;
        get diagnostics CONDITION 1
          i_code = returned_sqlstate, i_msg = message_text;
      END;
      UPDATE t_datetime
      SET log_time = IFNULL(f_log_time,NULL),
          end_time = IFNULL(f_end_time,NULL)
      WHERE id = f_id;
      IF i_con1 = 0 THEN
        get diagnostics i_rows = ROW_COUNT;
        SET @i_result = CONCAT("Update succeeded, affected ", i_rows,'.');
      ELSE
        SET @i_result = CONCAT('Update failed, error code is 1042, related message is ',i_msg,'.');
      END IF;
      SELECT @i_result;
    END$$


DELIMITER ;

  我们来执行上面的存储过程,完了后,就能把错误信息保存到SESSION 变量@i_result 中。这样,方便了后期进行各种输出。

CALL sp_do_update(1,NOW(),DATE_ADD(NOW(),INTERVAL 1 DAY));
Result.
Update succeeded, affected 1.


CALL sp_do_update(1,NULL,NULL);
Result.
Update failed, error code is 1042, related message is Column 'log_time' cannot be null.

posted @ 2012-08-08 09:49 顺其自然EVO 阅读(230) | 评论 (0)编辑 收藏

仅列出标题
共394页: First 上一页 300 301 302 303 304 305 306 307 308 下一页 Last 
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

常用链接

留言簿(55)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜