喜欢是淡淡的爱,爱是深深的喜欢!!

发布新日志

  • 怎么做json接口api的测试

    2017-06-03 16:02:39

     客户端joson格式接口aip测试
    接口通讯分为get和post请求两种,分享一个如何进行api接口的测试
    1、chrome浏览器输入http://apizza.cc/ 或者添加扩展程序
    2、进入创建一个项目

    举例说明:
    开发提供接口文档,跟进接口文档进行测试地址拼接以及测试用例设计
    如接口文档:

    如psot请求

    获取应答

    如GET请求


    获取应答


    可以根据相关情况设计测试用例进行接口API的测试。




  • 测试人员的核心价值(转)

    2012-10-10 16:53:59

    既然是“核心价值”,就应该能用一句话说清楚。关于软件测试的核心价值是什么,各种观点争论了很久,似乎很难得出一个明确的结论。这里有个很重要的原因,就是我们都深陷在测试工作的细节里面,没办法看清自己的位置和价值。不识庐山真面目,只缘身在此山中。

    要想搞清楚这个问题,我们必须走出围城来进行分析,如果把软件测试看成一种服务,那么从客户的视角来评判,最合适不过了。下面讲一件真实的事情。

    有一次我回家跟老友一起吃饭,聊起最近的工作。老友的单位是一家大企业,几个月前委托一家软件开发公司,开发了一套很大的企业管理软件。现在软件已经开发完成,进入了验收阶段。现在问题来了,负责验收软件的是信管部,部门老大非常担心软件的质量,希望能在验收签字前,把软件的严重质量问题都找出来,可是又不知道该从哪下手,如果能有一个权威的软件评测机构,对软件进行专业的测试,就最好了。

    “你们淘宝的软件测试,应该做的很专业吧,能不能帮我们来测试一下这个软件?你们接这种业务么?”老友提出这个问题。

    虽然淘宝测试现在还没有这种外接服务,不过这是一个难得的,饶有趣味的话题。

    “那你想要我们来测试哪些东西呢?哪些地方最担心?”

    “主要是性能吧,如果全公司人一起来用,不知道会不会出问题。还有就是数据的安全方面,公司的重要数据一定要绝对安全,不能被挖走。”

    “那软件的功能呢,功能需不需要我们来测一下?”

    “功能就不用了,我让我们部门的人来点点就行了。”

    听到这话我有点觉得不爽,不过想想倒也没必要跟老友去争辩这个问题,其实这确实是很多人对软件测试的看法。后来这个话题被岔开,没有继续谈下去了。

    所以下面的谈话并没有真实发生,是我用推理的方式,把讨论继续了下去,非常有趣。

    “功能测试并不是随便点点这么简单,淘宝的测试非常专业的,因为我们...”

    大家注意,精彩的地方到了,当我说出一个原因,并且能让老友信服,那就说明,这就是软件测试的核心价值了。

    “...我们的工程师对需求理解得很透彻,对业务很精通。”

    “我们部门的人对需求也很清楚的,因为他们就是最终的用户。”在平时的项目里我们也发现,无论需求分析做得多细致,软件交付以后,用户总能提出很多问题和改进意见,这是正常的,大可不必因此责怪测试工程师,因为没有人比用户更了解需求。最重要的是,不要让用户发现既严重又初级的Bug。

    “...我们编写的测试用例、文档非常专业非常完整,能够保证测试的质量。”

    “很好啊,你们很专业,不过这是你们内部的工作方式,我不是很关注的。”这里并不是否定测试文档的作用,只不过测试文档是测试团队的过程产物,无法直接给用户带来价值。

    “...我们对软件的架构设计非常了解,可以提前发现软件设计中的重要缺陷,避免返工。”

    “嗯,这个非常好,不过现在他们已经开发完了,要是在他们编码之前,请你们来对设计方案把把关,就好了。”用户非常希望能控制软件开发的全过程,而软件设计是最重要的里程碑,设计是否合格,直接影响后面的工作。

    “...我们能看懂代码,找出代码里的问题,不仅如此,我们还能修复Bug。”

    “好是好,不过代码量那么多,你们需要多少人来做啊?至于修复Bug,还是让他们自己改吧,不然我花那么多钱请他们干吗?”

    “...我们现在用的最好的测试管理工具,还有最好的自动化测试工具,可以把测试完全自动化。”

    “挺好,不过我还是担心需要的资源太多,自动化测试是挺好,你说说具体好在哪里呢?如果比手工测试成本低,就行。”同样的,用户对我们用什么方式测试并不特别关注,成本才是关键。

    “...我们的工程师工作效率很高,测试速度非常快,比你们部门的人要快50%。”

    “厉害!不过我们这里的薪水都比较低的,你们都是高薪IT,人月成本这么高,如果测的结果差不多,还是我自己找人来做吧。”

    “...我们的工程师都是专业人员,你的人只能发现一些表面的Bug,而我们能找出隐藏很深,并且很严重的Bug,这些Bug提早发现,能减少很多损失。”

    “有道理,其实我也担心他们这样点点,有些深层次问题发现不了,要是上线一段时间以后,大家才发现,那改都来不及了。”

    好了,这段虚拟的对话就到此为止,下面我们来做一些分析。先看一下最开始提到的性能测试和安全测试,这两个测试类型有一定的技术壁垒,因此性能和安全的Bug,不是每个人随便就能发现的。另外虚拟对话中提到,发现软件设计方案中的问题,也非常有难度。而功能测试的门槛相对较低,即使没受过训练,一般人也能发现一些初级的Bug,这让很多人产生一个错觉:“一般人”都能做功能测试。

    要证明这个错觉不成立,其实也挺容易,那就是看测试人员所发现的Bug,与“一般人”有哪些不同。如果找不到明显的不同,那错觉就变成了现实,如果测试人员没发现的Bug,让一般人或者用户发现,那就更杯具了。由此我们推理出测试的核心价值:

    能发现一般人发现不了的Bug!

    这句话看起来非常简单朴实,但是包含了很多因素。目前淘宝测试团队所设定的金Bug大奖(Gold Bug Award),就是为了鼓励测试工程师体现这一核心价值。

    有的测试工程师,由于项目时间太紧,开发匆忙赶出的代码质量又不合格,所以大部分时间都纠缠在初级的Bug里面,根本没时间、没精力去关注深层次Bug,虽然做的很辛苦,也做了很多项目,但是成长很慢,原因就在这里。

    要解决这个问题,测试工程师一方面要加强对开发技术的学习,了解软件程序的内部结构,为发现深层Bug创造必要条件;另一方面,要想办法推动开发提高代码质量,让自己从初级Bug里解脱出来,为自己赢得更多的时间,来寻找深层Bug,并且总结发现Bug的技巧和经验。

    到这里我们对测试核心价值的讨论可以告一段落了,软件测试要体现核心价值,自说自话是没有意义的,只有把测试作为一种服务提供给客户,让客户来评判,测试才能发展得更好。现在淘宝测试团队也专门成立了TAAS团队(Testing As A Service),在测试服务化上进一步探索。

  • 制作测试流程(转)

    2012-04-13 09:43:50

    测试流程的制定不是一门科学,而有时看起来,它更像一门艺术,一个好的测试管理者其实在面对不同的公司,不同的研发阶段,会采用不同的测试流程, 。或是而同样的测试流程,为了真正达到执行的效果,执行的方法也可能不一样。

      实施测试流程一般都是有两个原因,:一是软件质量出现的了问题,虽然在某种程度上已经得到解决,但仍需要通过测试,把预防措施的方法找到并固化下来;还有另一个原因则种是软件研发的规模壮大,要求做的在流程上更加清晰,可靠更好。我个人从我自己的角度出发最怕以下一某些情况是让人非常头疼的,:一种情况是,是今天刚看了一本书,被告知说这样做是规范应该这样制定的,而明天就要引入进来,完全不考虑公司的实际情况;另一种情况是“苏联模式”,二是那种即某某大公司的测试流程如此制定是这样做的,我们也要采用相同的方法这样。其实流程没有最好的,只有适合自己的,规范的测试流程不一定会帮助研发成功,反而在某些情况下会弄不好羁绊到自己自己的工作。

      现在大多数测试人会犯一个共同的错误,往往——把流程设计的得很完美,但没有可操作性很差,无法帮助对于软件公司真正的目的——研发,并没有起到应有的作用成功,久而久之测试的重要性就无从谈起,测试团队也渐渐在公司变成次要部门,成为打杂的得不到应有的重视。

      在流程的设计过程中,最重要的问题在于是目当前项目的特点是什么,产品经常出什么样的哪些问题,需要做什么怎样的调整,现有测试团队能不能做这样的能否做作出调整,研发团队是不是会不会能接收接受?

      首先谈谈项目特点,按照项目特点,大致可以一般来说分成两类,:

      一种是长期进行的项目,这种项目有基本的框架,有核心的技术,应用比较稳定,这种项目要注重测试用例的积累与复用,同时也适合做单元测试自动化测试的积累;

      另一种是变更频度更高,灵活,规模不大的项目,如果做自动化测试则会出现二次开发的时间大于手工测试的时间,而且项目结束后测试用例在长期中也没有任何复用,在自动化测试人员普遍成本比较高的情况下,所以反而更适做功能测试

      虽然这两者可能在长远的目标上并不一致,但是引入测试管理平台,从测试需求,、测试设计,、缺陷管理等方面入手则是测试团队必备的技能。一个好的测试流程必需要有好的系统平台的支撑,如果你把测试流程设计的得很完美,跟如同小学语文教科书一样,但执行这样的流程在起来现有的资源的情况下是未免不现实,倒并非说详细的流程是洪水猛兽,只是对于一家软件公司来说,资源的限制仍然是瓶颈所在的。,那流程也就没有意义,一般来说一个执行的得好的测试流程必然会有好的平台,就像我以前所在国内的几家很有声名的软件公司,其测试平台要不是么是采购的,就要么是自己开发的,但最主要是要适合自己一套适合自身特点的流程平台起了非常积极的作用。在这里也给大家建议一些好的测试平台,比如Mercury Interactive的Test Director,、IBM的TestManager,、Silk的一些缺陷管理平台,这些平台大多都能充分满足测试团队的要求其实都能满足大家的要求。,当然,还有一些免费的开源工具也是可用的。但从长远的角度看,我还是更建议大家读者使用那些不仅仅只是满足缺陷管理的工具,而是要应该选择能集成测试需求,、测试设计,、测试用例,、缺陷管理的工具,最好也能满足自动化的集成的,什么样的产品能满足就不多说了,免得有打广告之嫌J,而商业软件,如MI或IBM的产品在这些方面都有较好的表现。

      项目特点决定流程的长期目标,但对于不同产品类型的公司,可能出现的问题往往会不一样同。,比如说在金蝶的EAS-BossBOSS,、或是在金山做的游戏软件,、亦或还是在阿里巴巴做电子商务,作为测试管理者,就要具体的问题都应该区别对待。

      对于EAS-Boss这样大型的软件产品,团队的规模比较大,核心技术比较稳定。但对于这样的这样的产品有以下一些特点:

      由于产品比较大,手工测试时重复的工作量特别大;

      引擎与产品框架比较稳定;

      编译与发布的流程比较固化;

      由于团队的规模比较大,接口特别多,集成测试风险特别高;。

      这样种产品的测试,主要是把大量的重复频度比较高的功能测试转化为自动化测试角本脚本,在开发过程中要注意,核心引擎与稳定的产品部分,尽可能使用测试框架形成单元测试集,;同时由于编译与发布固化,适合做每日编译, ,自动化的执行单元测试集与自动化的测试角本。在做这种测试流程时,同时还要注意引入强大的分析统计工具,比如代码覆盖与评审工具,内存检查与性能函数分析工具,出错表统计模块,达到发布,、测试执行与评估自动化、一体化。由于进行每日集成,接口的问题可以尽早的暴露出来,避免了后期集成的风险,。

      这一点每日集成对于大型项目非常重要。同时,由于测试的自动化,大部分的自动化测试角本在空闲的时间运行,测试组可以在进入手工测试时得到比较稳定的版本,及大极大的提升了团队开发与测试的执行效率,。但然而在这样的情况下,缺陷点是整个团队对研发,、测试体系的技术要求特别高,其本上不亚于有时甚至难过做一个大型的项目。这样的测试流程在,在中小团队比较难以实现比较困难,而关键就在于无法降低的成本比较高。下图就是一个稳定项目的测试流程图。

      游戏软件产品的测试流程又有不同。当你去带领这个测试团队一个游戏团队时,可能游戏核心引擎应该是比较相对稳定的,而游戏内部的故事情节可能会不断的变化,。这时你可把一些更加稳定的程序做成比较稳定的自动化回归测试,同时加强对不断变化的游戏情节的功能测试,同时注意这些功能是不是否会影响到其它相关的模块。同时在因此,游戏测试的过程中还有一些比较有其特殊性,主要表现以下几点:

    服务器的稳定性,网络流量,与安全是游戏最至关重要的,(往往有很多游戏不是不好玩,而是太不稳定);

      游戏由于有及时的即时更新,会经常在同时修改缺陷的时候,还在同一模块下增加新功能;

      好的网络游戏开发,其的功能必然会是迎合玩家的需求(游戏性分析)。;

      对于游戏软件产品来说,这些需要特别注意重点控制的点关键,要求测试团队必需要加强以下几个方面,性能测试,代码的融合、相关性影响面的判断、版本的变更与控制,还有游戏性的分析与测试。性能测试主要加强以下几点,则需要注意在并发下服务器的稳定性监控,、网络流量与游戏客户端在大场面下的表现。;而版本控制在游戏软件的过程中,其意义更多——则会避免已经改了的问题重复出现,或是改了更新上去问题还是存在,如何高效的合并代码,、合成游戏资源、图片与角本脚本还是一个比较难度很高的事情,尤其涉及到多个部门。;而游戏性测试主要是避免那种些与游戏风格相背的情况,或是开发团队累死累活拼命完成得功能性任务做出的功能没有可延续性。

      性能测试与版本控制,在大多数软件的测试流程中都会涉及,但是在不同的软件产品/项目中都有其特点。一般属于通用软件测试流程的部分,但而游戏性测试则需要对游戏感觉很好有比较深刻的了解,并由真正懂懂得的玩家的人来担任,。某些时候,他甚至可以不是一个很好的软件测试人员,但他一定是一个真正懂游戏的人,这里有一些扯远,但这里,本文稍后一节,将我会在后面会强调人的因素也决定了流程的实施。

      下图是游戏迭代开发模型图

      如果你去做电子商务,或是做门户,这些项目的适时性,高性能,复杂的功能会给你更高的技术要求,更高强的时间性效率挑战,对测试的设计,、执行,、与性能测试提出更高的要求。其实在大多数互联网公司经常会出现这样的情况:刚出去的功能又撤下来修改,或是性能达不到要求仍需要又要调优。许多一些人都会犯这样一个错,认为测试的时间不够,就只要测试执行,而忽略了其他几个环节就可以了,不做细致的分析与设计,为后续工作带来很大压力。其实,一个充分测试过的有质量保证的产品,可以减轻客服,、市场,、等各方面很多的压力。产品在用户和研发之间,反复,几次不如晚一些上提供给用户。从另外一方面看,这还需要测试主管能顶住某些压力。时间紧迫当然这不是理由,如何在流程上保证测试的需求分析,、用例的设计与研发在开发时同步进行是最重要的,这时你要加强早期的测试介入,明确卡住需求确认这一部分,。这样,在研发进入开发阶段时,测试团队也能进入测试设计,。当研发开发完成时,你测试团队事实上已经其本基本上完成了大部分的测试设计,并准备进入测试执行,。不要在开发提交后再去想如何测测试,抱怨之声也就不绝于耳了。这样才可能测试好一个时间比较紧的项目不管在用于测试的时间上,还是测试的质量上都无法满足要求。

      ,同时测试设计的很好,不仅可以节约测试执行的时间,也可以在反复提交的过程中,由于用例执行的一致性,保证了测试在多次的执行中的质量,;。同时也有发布的标准,一是缺陷的情况,二是用例的执行与覆盖。同时由于研发给的测试时间比较紧,所以有两件事情就必需作做好,:一是明确产品提交测试时间,并在研发延迟时给自己争取时间;二是在质量达不到要求的情况下,时间及时的做出反应,不要到最后在研发不了解项目质量的情况下建议研发延迟项目。为了达到上面的要求你必需要一个很好的测试平台,把设计,测试用例管理,执行与用例的联动,缺陷管理与报表统计打通,尽可能的利用平台解决事务性工作,降低流程执行的成本,。也就是说,既让测试人员可以集中精力去测试,同时又能够让研发管理人员随时获取正在进行测试的进度与质量,。当这些工作做到透明化时以后,就算让研发延迟发布,研发部门也会接收接受,。下图是这一阶段的大致流程

      在这里可以跟大家说一下,我就曾经在产品发布权限不在测试这里部门的情况下,成功的让研发决定推迟发布了大约一半以上的项目,。大多数的测试部门主管,很难顶住来自项目/技术经理的压力是有理由的,因为他们根本不了解你做了哪些工作。有时候一些情况下,看似不可能的事情任务要想做成完成,关键要看在于事情的技巧,。流程表示了只是一个大方向的东西,而且,你永远也无法将责任推卸给流程也许是对的,更多情况下,作为测试主管,需要但将做事的方法与风格可以影响到推广到测试流程的推广中。

      在测试互联网项目时,还有一个更重要的就是如何保证性能,。

      也许大家会说不就是性能测试并不是单独存在的。其实不是完全正确,如果有充足的优秀高手人力资源做性能测试当然很好,但性能测试也不能完全保证所有的项目完全没有性能问题都完美无缺,因此,项目投入期间,同时性能测试是一个这个费时费力的工作,所以往往都是一般在资源不足的情况下开展的。作为测试主管,更应该,要学会判断那些相对更加重要的问题项目影响面会更广,需要集中做性能测试。这时你也许会问,那么会有人问其它相对不太重要的项目与问题怎么办,?其实做过性能调优的人都知道,大部分的性能问题都是由于一两个弱智的SQL语句导致的,所以可以从流程上加强对SQL查询语句在I/O问题的跟踪与评审,,从而避免大部分性能问题。

      上面分析了不同公司根据上文的分析,不同类型的产品在测试流程上的是有很大差异的。这时,也许大家你也许可以把握测试流程大的方向了,但真正是否适合现有的测试团队与研发团队,则仍需要精心的调整,。当我遇到问题的时候,第一时间往往有时候不是去寻找流程不对的问题,而是通过现有资源与执行的方法可能需要着手来微调一下你的测试流程,直到发现问题的所在,并纪录下来,最后整合到原来设定的流程中。

      这样的情况大概常常发生:比如说在需求上的处理,可能会有很多的测试人员会经常指责需求人员撰写的文档非常粗糙不详细,无法进行测试,。好像在我的记忆中,需求人员常常就是被骂的得灰头土脸,但是经过这些年在测试岗位上的工作,我才渐渐发现,其实有时候需求人员更需要鼓励鼓励是比职责更加有效的工作方式。这可能是一个放之四海皆准的工作态度。,指责只能加深对立,。其实在验收需求时,由于当时大家也只是知道一个大概我们的需求人员也只能从大致上把握核了解,可能大多数情况下是:原则上没有问题就通过了,。但然而,这种不负责任的态度却是给我们在做的测试工作带来相当多的麻烦。也只有在这样的时候,这些问题才能真正暴露出来,从而使测试设计时就会发现很多问题并没有写清楚,。一般情况下,很多测试人员这时就多半会放弃手边的工作,并消极地认为认为无法做工作无法继续下去。,等东西出来,并将问题最终归结到是需求给的不详细,而大加指责。

    从我对测试主管工作的记事以来,就在印象中保留了这样的一幕。记的刚进一家公司时,我团队中的人员就开始经常会有下属跟我说抱怨,公司的需求工程师让我们太失望了。需求如何如何不好,然而,多少有些经验的我,当时我是把几家国内我服务过的顶尖公司情况作了一个简单的对比,的需求跟公司的需求人员的需求做了比较这时才发现,发现,原来我们公司的需求人员还是做的得不错的,。让测试人员把心态调整过来是测试主管的另外一件事。试问,,如果是你做需求作为需求工程师,是否会比他们做的好吗得更好??有了这样的基调,就可以让然后建议测试人员去总结不清楚的地方,给需求人员一个相对比较具体和明确的意见,这样顺利的了解了需求。

      其实有时候不是流程不对流程在这其中并没有太多值得指责的地方,而是相互的理解与支持,换位思考而对流程的执行态度,却是更加关键的。我们不得不学会如何换位思考,并更多地从他人的角度来看待这些问题。

      同样的问题还出现在还有需求变更上,很多测试人过不了这一关,。总是他们指责研发人员,让研发那些本来就已经恼火的软件工程师更加火冒三丈。换位思考一番,其实不难了解,,其实需求变更对研发工程师来说是更大的麻烦,。他们需要修改设计,、代码,相较而测试只要需改测试用例,他们的工作确实更加麻烦。简单来说,就ok了,其实大家要分析什么样的需求变更最可怕,而不是眉毛胡子一把抓,其实测试对需求变更并不可怕,怕的是只有在提交时才发现,导致测试时间不够,才会真正让测试人员心慌。这时需要从研发流程上保证变更及时的通知到测试就可以了行,。也许有人会说你也需要说:,说的倒很容易,如果研发不按照你的要求做怎么办!其实这里你只要用我所采用的方法是用数据说话,在项目进行时统计发生过多少次这样的事情,让研发管理层知道,让项目组之间有一个比较,。一方面,如果是一家公司重视质量的公司,必然会引起重视;另一方面,从质量管理部门角度本身出发,也应该推动公司重视质量。,随着时间的增加,需求变更给测试人员的反馈一定会有下降的趋势,。关键是测试不能抓住鸡毛就一直揪着不放宽容一些来看待身边的同事,要允许别人他们犯错,对于解决问题本身来说会大有裨益。只要趋势是好的就可以了。同时如果出现这样的情况并且极大影响到了测试进度,则要与研发部门沟通清楚,,如果研发不认可的情况下还可以请上级进行评估一下。

      上面说的是不同态度在同样流程下的实现不同结果,下面主要讲一下关于自身资源是否胜任做流程上规定的事情,某些工作,也许并不一定是测试部门的优势,而另外一些,则需要根据测试团队的基本能力和资源进行评估。比如像性能测试、SQL的trace、自动化功能测试、单元测试集成、游戏性测试。其实这些流程上的关键点,可能大多数从功能测试上来一路走来的测试人员是无法做的到,这时要善于利用资源,不一定要测试做,可以从通过流程上保正有人来做积极调动其它部门的同事。,或是找有能力的人来做,测试可以进行监控。其实这种技术含量高一点的测试,对人的因素要求更大高,可以借助研发团队一起来做会有更好的效果,。记的第一次做Oracle数据库性能监控时,就是请的Oracle的DBA专家帮助设计了性能参数,成功的地进行了关于Oracle应用的性能测试。现在国内的测试人员普遍的技术水平不高,严重的限制了测试的发展,希望测试的同行能真正的提升测试技术水平,把这些高难度的测试做起来,而不是仅仅只是工具上玩玩,。只有真正提升测试团队的技术含量,这样别人才会更信赖你,这也是我这么多年来的一点经验。如果你对开发很精通,、同时又精通对测试颇有研究,、善于诊断性能与架构上的问题,、经常会帮助研发部门解决一些他们无法解决的事情,、同时又还懂的如何做测试管理,并了解研发人员的心态,那就真得的找不出你还有不成功的理由了任何理由让人不对你刮目相看了。

  • (转)Selenium

    2012-03-26 18:21:56

    1.Selenium工具简介 

    Selenium是ThoughtWorks公司开发的一套基于WEB应用的验收测试工具,直接运行在浏览器中,模拟客户操作。它抽象出一系列命令来模块用户操作,比如open命令表示打开一个URL,click命令表示点击某个按钮。Selenium实际上将这些命令转化成实际的HTTP请求在浏览器中运行。本系列现在主要包括以下4款: 

       1. Selenium Core,它的优点是编写测试案例简单,并且支持绝大多数的浏览器,但缺点也同样明显,Selenium Core需要远程安装,Selenese 语言也限制了复杂案例的可能性,并且没有良好的外部扩展,这是些都会是致命的问题。 
       2. Selenium Grid:允许同时并行地、在不同的环境上运行多个测试任务,极大地加快Web 应用的功能测试。 
       3. Selenium IDE :支持并且只支持Firefox浏览器,属于Firefox的一个插件,支持的浏览器太少,而依附于Firefox 也不便于日后开展自动化测试,但是,它的录制快捷好用!并且有代码转换功能,可以把Selenium 语言测试用例转为C#,Java,PHP,Ruby,Prel,Groovy,Python等语言的测试案例,我建议使用Selenium IDE + FireBug 进行测试案例的编写,然后转为其他语言的测试用例后,再调用Selenium RC运行测试用例。 
       4. Selenium RC(Remote Control) 是本人推荐使用的工具,它支持很多浏览器,可以使用C#,Java 等语言编写测试案例,易于维护,同时提供了很好的扩展性,所以接下来将针对Selenium RC 作为默认的测试工具进行介绍。 

    所以这里建议采用Selenium IDE + Selenium RC + Firebug组合搭建Web应用自动化验收测试。 

    2.Selenium-IDE安装和使用 

    安装 

       1. Selenium官网(http://seleniumhq.org/)下载Selenium-IDE作为Firefox的插件进行安装 

    使用 

    1.Firefox工具栏,打开Selenium-IDE插件,如下图: 

     

    2.选择插件界面中右上角红色录制按钮(开始录制、停止录制都是此按钮),如下图,这里录制登陆集中管理工具的过程。 

     


    3.录制完成后,点击回放按钮可以对刚刚录制的脚本进行回放,这里可以调整回放速度。 

    4.可以将录制的脚本转换成C#,Java,PHP,Ruby,Prel,Groovy,Python等语言,这里选择Java,如下图: 

     


    转换后代码如下: 

    Java代码  收藏代码
    1. package com.example.tests;  
    2.   
    3. import com.thoughtworks.selenium.*;  
    4. import java.util.regex.Pattern;  
    5.   
    6. public class MasterLogin extends SeleneseTestCase {  
    7.     public void setUp() throws Exception {  
    8.         setUp("http://change-this-to-the-site-you-are-testing/""*chrome");  
    9.     }  
    10.     public void testUntitled 2() throws Exception {  
    11.         selenium.open("/gm/login.jsf");  
    12.         selenium.type("j_username""tongweb");  
    13.         selenium.type("j_password""tongweb");  
    14.         selenium.click("j_security_check");  
    15.         selenium.waitForPageToLoad("30000");  
    16.     }  
    17. }  



    5.上面转换的代码可以稍加修改便可配合RC使用,修改后代码如下: 

    Java代码  收藏代码
    1.  package com.example.tests;  
    2.   
    3. import com.thoughtworks.selenium.*;  
    4. import java.util.regex.Pattern;  
    5.   
    6. public class MasterLogin extends SeleneseTestCase {  
    7.     public void setUp() throws Exception {  
    8.         setUp("http://localhost:9060/""*firefox");                                        // Modify this line  
    9.     }  
    10.     public void testMasterLogin() throws Exception {  
    11.         selenium.open("/gm/login.jsf");  
    12.         selenium.type("j_username""tongweb");  
    13.         selenium.type("j_password""tongweb");  
    14.         selenium.click("j_security_check");  
    15.         selenium.waitForPageToLoad("30000");  
    16.         assertEquals("TongWeb管理控制台", selenium.getTitle());                    // Judging the testcase is pass or failed by add a assert.  
    17.     }  
    18. }  


    3.Selenium-RC安装使用 

    简介 

          Selenium RC(Remote Control)是一个基于java编写的开源测试工具,允许使用多种语言编写自动化的WEB UI测试用例。这个工具提供一个Selenium Server可以启动,停止和控制绝大多数主流浏览器,这个服务器使用AJAX直接和浏览器进行交互,可以使用HTTP GET/POST请求向Selenium Server发送命令。 

          Selenium-RC安装包包含两部分:Selenium-server、Selenium-client-driver(各语言版本分别对应一个 client-driver.jar),server为测试服务器,client-driver为测试用例的API(编写测试用例的使用用到)。 

     


    安装: 

       1. 安装1.5及以上版本的JDK 
       2. 官网下载最新版Selenium-RC组件至本地,直接解压便可使用 

    启动Server: 
    启动命令java -jar Selenium-server.jar 可以带参数启动,如java -jar selenium-server.jar -interactive为以交互模式启动,这里自己可以将启动操作制作成简单的.bat或.sh脚本,如bat脚本(此脚本与selenium- server.jar在同一级目录下): 

    Java代码  收藏代码
    1.  @echo off  
    2. rem ---------------------------------------------------------------------------  
    3. rem   Start Selenium Server  
    4. rem   Add by pengyao 08/26/2010  
    5. rem ---------------------------------------------------------------------------  
    6.   
    7. java -jar selenium-server.jar  


    开发运行测试用例: 

    这里采用java语言为例来进行说明: 

       1. 解压Selenium-RC压缩包,取出selenium-java-client-driver.jar 
       2. 打开Java IDE(Eclipse, NetBeans, IntelliJ, Netweaver, etc.) 
       3. 新建一个project 
       4. 将selenium-java-client-driver.jar导入此project的classpath 
       5. 将Selenium-IDE录制好的html脚本转换成java文件,导入新建的project(可能需要稍作修改,如添加assert判断用例是否测试通过),或直接使用selenium-java-client API编写测试用例。本工具同时支持Junit和TestNg测试框架。 
       6. 启动Selenium Server 
       7. 在Java IDE 或命令行执行编写好的测试用例 

    4.Selenium-RC工作原理 

     


    (1).测试案例(Testcase)通过Client Lib的接口向Selenium Server发送Http请求,要求和Selenium Server建立连接。 

    (2).Selenium Server的Launcher启动浏览器,把Selenium Core加载入浏览器页面当中,并把浏览器的代理设置为Selenium Server的Http Proxy。 

    (3).测试案例通过Client Lib的接口向Selenium Server发送Http请求,Selenium Server对请求进行解析,然后通过Http Proxy发送JS命令通知Selenium Core执行操作浏览器的动作。 

    (4).Selenium Core接收到指令后,执行操作。 

    (5).浏览器收到新的页面请求信息(因为在(4)中,Selenium Core的操作可能引发新的页面请求),于是发送Http请求,请求新的Web页面。 
    由于Selenium Server在启动浏览器时做了手脚,所以Selenium Server会接收到所有由它启动的浏览器发送的请求。 

    (6).Selenium Server接收到浏览器的发送的Http请求后,自己重组Http请求,获取对应的Web页面。 

    (7).Selenium Server的Http Proxy把接收的Web页面返回给浏览器。 

    5.Selenium-RC优缺点 

    与Watij对比的优势: 
    编号 Selenium Watij 
    1 跨浏览器 仅IE 
    2 跨操作系统 仅Windows 
    3 支持多数主流编程语言C#,Java,PHP,Ruby,Prel,Groovy,Python等 仅java 
    4 提供Hudson插件,容易与Hudson整合 未提供Hudson插件 
    5 可以录制和回放脚本,并可以将录制好的脚本转换成各种主流编程语言 未提供此功能 
    6 API强大,文档全 ? 
    7 属于同类产品中主流,官方资料详细、拥有正规的中文论坛 较主流,资料不是特别多 
    8 发展迅速 发展缓慢,出现替代产品 
  • MonkeyRunner.java

    2012-03-19 15:05:42

    路径:android-2.2-froyo/com/android/monkeyrunner/MonkeyRunner.java

    /**
     * Copyright (C) 2009 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    package com.android.monkeyrunner;
    
    import com.android.ddmlib.AndroidDebugBridge;
    import com.android.ddmlib.IDevice;
    import com.android.ddmlib.Log;
    import com.android.ddmlib.NullOutputReceiver;
    import com.android.ddmlib.RawImage;
    import com.android.ddmlib.Log.ILogOutput;
    import com.android.ddmlib.Log.LogLevel;
    
    import java.awt.image.BufferedImage;
    
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import java.net.InetAddress;
    import java.net.Socket;
    import java.net.UnknownHostException;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    
    import javax.imageio.ImageIO;
    
    /***
     *  MonkeyRunner is a host side application to control a monkey instance on a
     *  device. MonkeyRunner provides some useful helper functions to control the
     *  device as well as various other methods to help script. tests. 
     */
    public class MonkeyRunner {
    
      static String monkeyServer = "127.0.0.1";
      static int monkeyPort = 1080;
      static Socket monkeySocket = null;
    
      static IDevice monkeyDevice;
    
      static BufferedReader monkeyReader;
      static BufferedWriter monkeyWriter;
      static String monkeyResponse;
    
      static MonkeyRecorder monkeyRecorder;
    
      static String scriptName = null;
      
      // Obtain a suitable logger.
      private static Logger logger = Logger.getLogger("com.android.monkeyrunner");
    
      // delay between key events
      final static int KEY_INPUT_DELAY = 1000;
      
      // version of monkey runner
      final static String monkeyRunnerVersion = "0.4";
    
      // TODO: interface cmd; class xml tags; fix logger; test class/script.
      public static void main(String[] args) throws IOException {
    
        // haven't figure out how to get below INFO...bad parent.  Pass -v INFO to turn on logging 
        logger.setLevel(Level.parse("WARNING"));  
        processOptions(args);
        
        logger.info("initAdb");
        initAdbConnection();
        logger.info("openMonkeyConnection");
        openMonkeyConnection();
    
        logger.info("start_script");
        start_script();
        
        logger.info("ScriptRunner.run");
        ScriptRunner.run(scriptName);
       
        logger.info("end_script");
        end_script();
        logger.info("closeMonkeyConnection");
        closeMonkeyConnection();  
      }
    
      /***
       *  Initialize an adb session with a device connected to the host
       * 
       */
      public static void initAdbConnection() {
        String adbLocation = "adb";
        boolean device = false;
        boolean emulator = false;
        String serial = null;
    
        AndroidDebugBridge.init(false /** debugger support */);
    
        try {
          AndroidDebugBridge bridge = AndroidDebugBridge.createBridge(
              adbLocation, true /** forceNewBridge */);
    
          // we can't just ask for the device list right away, as the internal thread getting
          // them from ADB may not be done getting the first list.
          // Since we don't really want getDevices() to be blocking, we wait here manually.
          int count = 0;
          while (bridge.hasInitialDeviceList() == false) {
            try {
              Thread.sleep(100);
              count++;
            } catch (InterruptedException e) {
              // pass
            }
    
            // let's not wait > 10 sec.
            if (count > 100) {
              System.err.println("Timeout getting device list!");
              return;
            }
          }
    
          // now get the devices
          IDevice[] devices = bridge.getDevices();
    
          if (devices.length == 0) {
            printAndExit("No devices found!", true /** terminate */);
          }
    
          monkeyDevice = null;
    
          if (emulator || device) {
            for (IDevice d : devices) {
              // this test works because emulator and device can't both be true at the same
              // time.
              if (d.isEmulator() == emulator) {
                // if we already found a valid target, we print an error and return.
                if (monkeyDevice != null) {
                  if (emulator) {
                    printAndExit("Error: more than one emulator launched!",
                        true /** terminate */);
                  } else {
                    printAndExit("Error: more than one device connected!",true /** terminate */);
                  }
                }
                monkeyDevice = d;
              }
            }
          } else if (serial != null) {
            for (IDevice d : devices) {
              if (serial.equals(d.getSerialNumber())) {
                monkeyDevice = d;
                break;
              }
            }
          } else {
            if (devices.length > 1) {
              printAndExit("Error: more than one emulator or device available!",
                  true /** terminate */);
            }
            monkeyDevice = devices[0];
          }
    
          monkeyDevice.createForward(monkeyPort, monkeyPort);
          String command = "monkey --port " + monkeyPort;
          monkeyDevice.executeShellCommand(command, new NullOutputReceiver());
    
        } catch(IOException e) {
          e.printStackTrace();
        }
      }
    
      /***
       * Open a tcp session over adb with the device to communicate monkey commands
       */
      public static void openMonkeyConnection() {
        try {
          InetAddress addr = InetAddress.getByName(monkeyServer);
          monkeySocket = new Socket(addr, monkeyPort);
          monkeyWriter = new BufferedWriter(new OutputStreamWriter(monkeySocket.getOutputStream()));
          monkeyReader = new BufferedReader(new InputStreamReader(monkeySocket.getInputStream()));
        } catch (UnknownHostException e) {
          e.printStackTrace();
        } catch(IOException e) {
          e.printStackTrace();
        }
      }
      
      /*** 
       * Close tcp session with the monkey on the device
       * 
       */
      public static void closeMonkeyConnection() {
        try {
          monkeyReader.close();
          monkeyWriter.close();
          monkeySocket.close();
          AndroidDebugBridge.terminate();
        } catch(IOException e) {
          e.printStackTrace();
        }
      }
    
      /*** 
       * This is a house cleaning routine to run before starting a script. Puts
       * the device in a known state and starts recording interesting info.
       */
      public static void start_script() throws IOException {
        press("menu", false);
        press("menu", false);
        press("home", false);
        
        // Start recording the script. output, might want md5 signature of file for completeness
        monkeyRecorder = new MonkeyRecorder(scriptName, monkeyRunnerVersion);
    
        // Record what device we are running on
        addDeviceVars();
        monkeyRecorder.addComment("Script. commands");
      }
    
      /*** 
       * This is a house cleaning routine to run after finishing a script.
       * Puts the monkey server in a known state and closes the recording.
       */
      public static void end_script() throws IOException {
        String command = "done";
        sendMonkeyEvent(command, false, false);
        
        // Stop the recording and zip up the results
        monkeyRecorder.close();
      }
    
      /*** This is a method for scripts to launch an activity on the device
       * 
       * @param name The name of the activity to launch 
       */
      public static void launch_activity(String name) throws IOException {
        System.out.println("Launching: " + name);
        recordCommand("Launching: " + name);
        monkeyDevice.executeShellCommand("am start -a android.intent.action.MAIN -n " 
            + name, new NullOutputReceiver());
        // void return, so no response given, just close the command element in the xml file.
        monkeyRecorder.endCommand();
       }
    
      /***
       * Grabs the current state of the screen stores it as a png
       * 
       * @param tag filename or tag descriptor of the screenshot
       */
      public static void grabscreen(String tag) throws IOException {
        tag += ".png";
    
        try {
          Thread.sleep(1000);
          getDeviceImage(monkeyDevice, tag, false);
        } catch (InterruptedException e) {
        }
      }
    
      /***
       * Sleeper method for script. to call
       * 
       * @param msec msecs to sleep for
       */
      public static void sleep(int msec) throws IOException {
        try {
          recordCommand("sleep: " + msec);
          Thread.sleep(msec);
          recordResponse("OK");
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    
      /***
       * Tap function for scripts to call at a particular x and y location
       * 
       * @param x x-coordinate
       * @param y y-coordinate
       */
      public static boolean tap(int x, int y) throws IOException {
        String command = "tap " + x + " " + y;
        boolean result = sendMonkeyEvent(command);
        return result;
      }
    
      /*** 
       * Press function for scripts to call on a particular button or key
       * 
       * @param key key to press
       */
      public static boolean press(String key) throws IOException {
        return press(key, true);
      }
    
      /*** 
       * Press function for scripts to call on a particular button or key
       * 
       * @param key key to press
       * @param print whether to send output to user
       */
      private static boolean press(String key, boolean print) throws IOException {
        String command = "press " + key;
        boolean result = sendMonkeyEvent(command, print, true);
        return result;
      }
    
      /***
       * dpad down function
       */
      public static boolean down() throws IOException {
        return press("dpad_down");
      }
    
      /***
       * dpad up function
       */
      public static boolean up() throws IOException {
        return press("dpad_up");
      }
    
      /***
       * Function to type text on the device
       * 
       * @param text text to type
       */
      public static boolean type(String text) throws IOException {
        boolean result = false;
        // text might have line ends, which signal new monkey command, so we have to eat and reissue
        String[] lines = text.split("[\\r\\n]+");
        for (String line: lines) {
          result = sendMonkeyEvent("type " + line + "\n");
        }
        // return last result.  Should never fail..?
        return result;
      }
      
      /***
       * Function to get a static variable from the device
       * 
       * @param name name of static variable to get
       */
      public static boolean getvar(String name) throws IOException {
        return sendMonkeyEvent("getvar " + name + "\n");
      }
    
      /***
       * Function to get the list of static variables from the device
       */
      public static boolean listvar() throws IOException {
        return sendMonkeyEvent("listvar \n");
      }
    
      /***
       * This function is the communication bridge between the host and the device.
       * It sends monkey events and waits for responses over the adb tcp socket.
       * This version if for all scripted events so that they get recorded and reported to user.
       * 
       * @param command the monkey command to send to the device
       */
      private static boolean sendMonkeyEvent(String command) throws IOException {
        return sendMonkeyEvent(command, true, true);
      }
    
      /***
       * This function allows the communication bridge between the host and the device
       * to be invisible to the script. for internal needs.
       * It splits a command into monkey events and waits for responses for each over an adb tcp socket.
       * Returns on an error, else continues and sets up last response.
       * 
       * @param command the monkey command to send to the device
       * @param print whether to print out the responses to the user
       * @param record whether to put the command in the xml file that stores test outputs
       */
      private static boolean sendMonkeyEvent(String command, Boolean print, Boolean record) throws IOException {
        command = command.trim();
        if (print)
          System.out.println("MonkeyCommand: " + command);
        if (record)
          recordCommand(command);
        logger.info("Monkey Command: " + command + ".");
          
        // send a single command and get the response
        monkeyWriter.write(command + "\n");
        monkeyWriter.flush();
        monkeyResponse = monkeyReader.readLine();
    
        if(monkeyResponse != null) {
          // if a command returns with a response
          if (print)
            System.out.println("MonkeyServer: " + monkeyResponse);
          if (record)
            recordResponse(monkeyResponse);
          logger.info("Monkey Response: " + monkeyResponse + ".");
    
          // return on error
          if (monkeyResponse.startsWith("ERROR"))
            return false;
    
          // return on ok
          if(monkeyResponse.startsWith("OK"))
            return true;
    
          // return on something else?
          return false;
        }
        // didn't get a response...
        if (print)
          System.out.println("MonkeyServer: ??no response");
        if (record)
          recordResponse("??no response");
        logger.info("Monkey Response: ??no response.");
    
        //return on no response
        return false;
      }
    
      /***
       * Record the command in the xml file
       *
       * @param command the command sent to the monkey server
       */
      private static void recordCommand(String command) throws IOException {
        if (monkeyRecorder != null) {                       // don't record setup junk
          monkeyRecorder.startCommand();
          monkeyRecorder.addInput(command);
        }
      }
      
      /***
       * Record the response in the xml file
       *
       * @param response the response sent by the monkey server
       */
      private static void recordResponse(String response) throws IOException {
        recordResponse(response, "");
      } 
      
      /***
       * Record the response and the filename in the xml file, store the file (to be zipped up later)
       *
       * @param response the response sent by the monkey server
       * @param filename the filename of a file to be time stamped, recorded in the xml file and stored
       */
      private static void recordResponse(String response, String filename) throws IOException {
        if (monkeyRecorder != null) {                    // don't record setup junk
          monkeyRecorder.addResult(response, filename);  // ignores file if filename empty
          monkeyRecorder.endCommand();
        }
      }
        
      /***
       * Add the device variables to the xml file in monkeyRecorder.
       * The results get added as device_var tags in the script_run tag
       */
      private static void addDeviceVars() throws IOException {
        monkeyRecorder.addComment("Device specific variables");
        sendMonkeyEvent("listvar \n", false, false);
        if (monkeyResponse.startsWith("OK:")) {
          // peel off "OK:" string and get the individual var names  
          String[] varNames = monkeyResponse.substring(3).split("\\s+");
          // grab all the individual var values
          for (String name: varNames) {
            sendMonkeyEvent("getvar " + name, false, false);
            if(monkeyResponse != null) {
              if (monkeyResponse.startsWith("OK") ) {
                if (monkeyResponse.length() > 2) {
                  monkeyRecorder.addDeviceVar(name, monkeyResponse.substring(3));
                } else { 
                  // only got OK - good variable but no value
                  monkeyRecorder.addDeviceVar(name, "null");
                }
              } else { 
                // error returned - couldn't get var value for name... include error return
                monkeyRecorder.addDeviceVar(name, monkeyResponse);
              }
            } else { 
              // no monkeyResponse - bad variable with no value
              monkeyRecorder.addDeviceVar(name, "null");
            }
          }
        } else {
          // it's an error, can't find variable names...
          monkeyRecorder.addAttribute("listvar", monkeyResponse);
        }
      }
      
      /***
       * Process the command-line options
       *
       * @return Returns true if options were parsed with no apparent errors.
       */
      private static void processOptions(String[] args) {
        // parse command line parameters.
        int index = 0;
    
        do {
          String argument = args[index++];
    
          if ("-s".equals(argument)) {
            if(index == args.length) {
              printUsageAndQuit("Missing Server after -s");
            }
    
            monkeyServer = args[index++];
    
          } else if ("-p".equals(argument)) {
            // quick check on the next argument.
            if (index == args.length) {
              printUsageAndQuit("Missing Server port after -p");
            }
    
            monkeyPort = Integer.parseInt(args[index++]);
    
          } else if ("-v".equals(argument)) {
            // quick check on the next argument.
            if (index == args.length) {
              printUsageAndQuit("Missing Log Level after -v");
            }
    
            Level level = Level.parse(args[index++]);
            logger.setLevel(level);
            level = logger.getLevel();
            System.out.println("Log level set to: " + level + "(" + level.intValue() + ").");
            System.out.println("Warning: Log levels below INFO(800) not working currently... parent issues");
            
          } else if (argument.startsWith("-")) {
            // we have an unrecognized argument.
            printUsageAndQuit("Unrecognized argument: " + argument + ".");
    
            monkeyPort = Integer.parseInt(args[index++]);
    
          } else {
            // get the filepath of the script. to run.  This will be the last undashed argument.
            scriptName = argument;
          }
        } while (index < args.length);
      }
    
      /**
       * Grab an image from an ADB-connected device.
       */
      private static void getDeviceImage(IDevice device, String filepath, boolean landscape)
      throws IOException {
        RawImage rawImage;
        recordCommand("grabscreen");
        System.out.println("Grabbing Screeshot: " + filepath + ".");
    
        try {
          rawImage = device.getScreenshot();
        }
        catch (IOException ioe) {
          recordResponse("No frame. buffer", "");
          printAndExit("Unable to get frame. buffer: " + ioe.getMessage(), true /** terminate */);
          return;
        }
    
        // device/adb not available?
        if (rawImage == null) {
          recordResponse("No image", "");
          return;
        }
        
        assert rawImage.bpp == 16;
    
        BufferedImage image;
        
        logger.info("Raw Image - height: " + rawImage.height + ", width: " + rawImage.width);
    
        if (landscape) {
          // convert raw data to an Image
          image = new BufferedImage(rawImage.height, rawImage.width,
              BufferedImage.TYPE_INT_ARGB);
    
          byte[] buffer = rawImage.data;
          int index = 0;
          for (int y = 0 ; y < rawImage.height ; y++) {
            for (int x = 0 ; x < rawImage.width ; x++) {
    
              int value = buffer[index++] & 0x00FF;
              value |= (buffer[index++] << 8) & 0x0FF00;
    
              int r = ((value >> 11) & 0x01F) << 3;
              int g = ((value >> 5) & 0x03F) << 2;
              int b = ((value >> 0) & 0x01F) << 3;
    
              value = 0xFF << 24 | r << 16 | g << 8 | b;
    
              image.setRGB(y, rawImage.width - x - 1, value);
            }
          }
        } else {
          // convert raw data to an Image
          image = new BufferedImage(rawImage.width, rawImage.height,
              BufferedImage.TYPE_INT_ARGB);
    
          byte[] buffer = rawImage.data;
          int index = 0;
          for (int y = 0 ; y < rawImage.height ; y++) {
            for (int x = 0 ; x < rawImage.width ; x++) {
    
              int value = buffer[index++] & 0x00FF;
              value |= (buffer[index++] << 8) & 0x0FF00;
    
              int r = ((value >> 11) & 0x01F) << 3;
              int g = ((value >> 5) & 0x03F) << 2;
              int b = ((value >> 0) & 0x01F) << 3;
    
              value = 0xFF << 24 | r << 16 | g << 8 | b;
    
              image.setRGB(x, y, value);
            }
          }
        }
    
        if (!ImageIO.write(image, "png", new File(filepath))) {
          recordResponse("No png writer", "");
          throw new IOException("Failed to find png writer");
        }
        recordResponse("OK", filepath);
      }
    
      private static void printUsageAndQuit(String message) {
        // 80 cols marker:  01234567890123456789012345678901234567890123456789012345678901234567890123456789
        System.out.println(message);
        System.out.println("Usage: monkeyrunner [options] SCRIPT_FILE");
        System.out.println("");
        System.out.println("    -s      MonkeyServer IP Address.");
        System.out.println("    -p      MonkeyServer TCP Port.");
        System.out.println("    -v      MonkeyServer Logging level (ALL, FINEST, FINER, FINE, CONFIG, INFO, WARNING, SEVERE, OFF)");
        System.out.println("");
        System.out.println("");
    
        System.exit(1);
      }
    
      private static void printAndExit(String message, boolean terminate) {
        System.out.println(message);
        if (terminate) {
          AndroidDebugBridge.terminate();
        }
        System.exit(1);
      }
    }

  • ScriptRunner.java

    2012-03-19 15:05:42

    路径:android-2.2-froyo/com/android/monkeyrunner/ScriptRunner.java

    package com.android.monkeyrunner;
    
    import org.python.core.Py;
    import org.python.core.PyObject;
    import org.python.util.PythonInterpreter;
    import org.python.util.InteractiveConsole;
    
    import java.io.File;
    import java.io.IOException;
    import java.io.FileInputStream;
    import java.lang.RuntimeException;
    import java.util.Properties;
    
    
    /***
     * Runs Jython based scripts.
     */
    public class ScriptRunner {
    
      /*** The "this" scope object for scripts. */
      private final Object scope;
      private final String variable;
      
      /*** Private constructor. */
      private ScriptRunner(Object scope, String variable) {
        this.scope = scope;
        this.variable = variable;
      }
      
      /*** Creates a new instance for the given scope object. */
      public static ScriptRunner newInstance(Object scope, String variable) {
        return new ScriptRunner(scope, variable);
      }
    
      /***
       * Runs the specified Jython script. First runs the initialization script. to
       * preload the appropriate client library version.
       */
      public static void run(String scriptfilename) {
        try {
          initPython();
          PythonInterpreter python = new PythonInterpreter();
          
          python.execfile(scriptfilename);
        } catch(Exception e) {
          e.printStackTrace();
        }
      }
      
    
      /*** Initialize the python interpreter. */
      private static void initPython() {
        Properties props = new Properties();
        // Default is 'message' which displays sys-package-mgr bloat
        // Choose one of error,warning,message,comment,debug
        props.setProperty("python.verbose", "error");
        props.setProperty("python.path", System.getProperty("java.class.path"));
        PythonInterpreter.initialize(System.getProperties(), props, new String[] {""});
      }
    
      /***
       * Create and run a console using a new python interpreter for the test
       * associated with this instance.
       */
      public void console() throws IOException {
        initPython();
        InteractiveConsole python = new InteractiveConsole();
        initInterpreter(python, scope, variable);
        python.interact();
      }
    
      /***
       * Start an interactive python interpreter using the specified set of local
       * variables. Use this to interrupt a running test script. with a prompt:
       * 
       * @param locals
       */
      public static void console(PyObject locals) {
        initPython();
        InteractiveConsole python = new InteractiveConsole(locals);
        python.interact();
      }
    
      /***
       * Initialize a python interpreter.
       * 
       * @param python
       * @param scope
       * @throws IOException
       */
      public static void initInterpreter(PythonInterpreter python, Object scope, String variable) 
          throws IOException {
        // Store the current test case as the this variable
        python.set(variable, scope);
      }
    }
  • MonkeyRecorder.java源码

    2012-03-19 15:01:48

    路径:android-2.2-froyo/com/android/monkeyrunner/MonkeyRecorder.java

    /**
     * Copyright (C) 2009 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    package com.android.monkeyrunner;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.List;
    import java.util.ArrayList;
    import java.util.Calendar;
    import java.util.zip.ZipEntry;
    import java.util.zip.ZipOutputStream;
    
    import org.jheer.XMLWriter;
    
    /***
     *  MonkeyRecorder is a host side class that records the output of scripts that are run. 
     *  It creates a unique directory, puts in an xml file that records each cmd and result.
     *  It stores every screenshot in this directory.
     *  When finished, it zips this all up.
     *
     *  Calling Sequence:
     *    mr = new MonkeyRecorder(scriptName);
     *    mr.startCommand();
     *    [mr.addAttribute(name, value);]
     *    ...
     *    [mr.addInput(cmd);]
     *    [mr.addResults(result, filename);]   // filename = "" if no screenshot
     *    mr.endCommand();
     *    mr.addComment(comment);
     *    mr.startCommand();
     *    ...
     *    mr.endCommand();
     *    ...
     *    mr.close();
     *
     *  With MonkeyRunner this should output an xml file, <script_name>-yyyyMMdd-HH:mm:ss.xml, into the
     *  directory out/<script_name>-yyyyMMdd-HH:mm:ss with the contents like:
     *
     *  <?xml version="1.0" encoding='UTF-8'?>
     *  <!-- Monkey Script. Results -->
     *  <script_run script_name="filename" monkeyRunnerVersion="0.2">
     *    <!-- Device specific variables -->
     *    <device_var var_name="name" var_value="value" />
     *    <device_var name="build.display" value="opal-userdebug 1.6 DRC79 14207 test-keys"/>
     *    ...
     *    <!-- Script. commands -->
     *    <command>
     *      dateTime="20090921-17:08:43"
     *      <input cmd="Pressing: menu"/>
     *      <response result="OK" dateTime="20090921-17:08:43"/>
     *    </command>
     *    ...
     *    <command>
     *      dateTime="20090921-17:09:44"
     *      <input cmd="grabscreen"/>
     *      <response result="OK" dateTime="20090921-17:09:45" screenshot="home_screen-20090921-17:09:45.png"/>
     *    </command>
     *    ...
     *  </script_run>
     *  
     *  And then zip it up with all the screenshots in the file: <script_name>-yyyyMMdd-HH:mm:ss.zip.
     */
     
    public class MonkeyRecorder {
    
      // xml file to store output results in
      private static String mXmlFilename;
      private static FileWriter mXmlFile;
      private static XMLWriter mXmlWriter;
      
      // unique subdirectory to put results in (screenshots and xml file)
      private static String mDirname;
      private static List<String> mScreenShotNames = new ArrayList<String>();
      
      // where we store all the results for all the script. runs
      private static final String ROOT_DIR = "out";
      
      // for getting the date and time in now()
      private static final SimpleDateFormat SIMPLE_DATE_TIME_FORMAT =
          new SimpleDateFormat("yyyyMMdd-HH:mm:ss");
      
      /***
       * Create a new MonkeyRecorder that records commands and zips up screenshots for submittal
       * 
       * @param scriptName filepath of the monkey script. we are running
       */
      public MonkeyRecorder(String scriptName, String version) throws IOException {
        // Create directory structure to store xml file, images and zips
        File scriptFile = new File(scriptName);
        scriptName = scriptFile.getName();  // Get rid of path
        mDirname = ROOT_DIR + "/" + stripType(scriptName) + "-" + now();
        new File(mDirname).mkdirs();
        
        // Initialize xml file
        mXmlFilename = stampFilename(stripType(scriptName) + ".xml");
        initXmlFile(scriptName, version);
      }
    
      // Get the current date and time in a simple string format (used for timestamping filenames)
      private static String now() {
        return SIMPLE_DATE_TIME_FORMAT.format(Calendar.getInstance().getTime());     
      }
      
      /***
       * Initialize the xml file writer
       * 
       * @param scriptName filename (not path) of the monkey script, stored as attribute in the xml file
       * @param version of the monkey runner test system
       */
      private static void initXmlFile(String scriptName, String version) throws IOException {
        String[] names = new String[] { "script_name", "monkeyRunnerVersion" };
        String[] values = new String[] { scriptName, version };
        mXmlFile = new FileWriter(mDirname + "/" + mXmlFilename);
        mXmlWriter = new XMLWriter(mXmlFile);
        mXmlWriter.begin();
        mXmlWriter.comment("Monkey Script. Results");
        mXmlWriter.start("script_run", names, values, names.length);
      }
      
      /***
       * Add a comment to the xml file.
       * 
       * @param comment comment to add to the xml file
       */
      public static void addComment(String comment) throws IOException {
        mXmlWriter.comment(comment);
      }
        
      /***
       * Begin writing a command xml element
       */
      public static void startCommand() throws IOException {
        mXmlWriter.start("command", "dateTime", now());
      }
      
      /***
       * Write a command name attribute in a command xml element.  
       * It's add as a sinlge script. command could be multiple monkey commands.
       * 
       * @param cmd command sent to the monkey
       */
      public static void addInput(String cmd)  throws IOException {
        String name = "cmd";
        String value = cmd;
        mXmlWriter.tag("input", name, value);
      }
      
      /***
       * Write a response xml element in a command.  
       * Attributes include the monkey result, datetime, and possibly screenshot filename
       * 
       * @param result response of the monkey to the command
       * @param filename filename of the screen shot (or other file to be included)
       */
      public static void addResult(String result, String filename) throws IOException {
        int num_args = 2;
        String[] names = new String[3];
        String[] values = new String[3];
        names[0] = "result";
        values[0] = result;
        names[1] = "dateTime";
        values[1] = now();
        if (filename.length() != 0) {
          names[2] = "screenshot";
          values[2] = stampFilename(filename); 
          addScreenShot(filename);
          num_args = 3;
        }
        mXmlWriter.tag("response", names, values, num_args); 
      }
      
      /***
       * Add an attribut to an open xml element. name="escaped_value"
       * 
       * @param name name of the attribute
       * @param value value of the attribute
       */
      public static void addAttribute(String name, String value) throws IOException {
        mXmlWriter.addAttribute(name, value);
      }
    
       /***
       * Add an xml device variable element. name="escaped_value"
       * 
       * @param name name of the variable
       * @param value value of the variable
       */
      public static void addDeviceVar(String name, String value) throws IOException {
        String[] names = {"name", "value"};
        String[] values = {name, value};
        mXmlWriter.tag("device_var", names, values, names.length);
      }
     
      /***
       * Move the screenshot to storage and remember you did it so it can be zipped up later.
       * 
       * @param filename file name of the screenshot to be stored (Not path name)
       */
      private static void addScreenShot(String filename) {
        File file = new File(filename);
        String screenShotName = stampFilename(filename);
        file.renameTo(new File(mDirname, screenShotName));
        mScreenShotNames.add(screenShotName);
      }
    
      /***
       * Finish writing a command xml element
       */
      public static void endCommand() throws IOException {
        mXmlWriter.end();
      }
      
      /***
       * Add datetime in front of filetype (the stuff after and including the last infamous '.')
       *
       * @param filename path of file to be stamped
       */
      private static String stampFilename(String filename) {
        // 
        int typeIndex = filename.lastIndexOf('.');
        if (typeIndex == -1) {
          return filename + "-" + now();
        }  
        return filename.substring(0, typeIndex) + "-" + now() + filename.substring(typeIndex);
      }
      
      /***
       * Strip out the file type (the stuff after and including the last infamous '.')
       *
       * @param filename path of file to be stripped of type information
       */
       private static String stripType(String filename) {
        // 
        int typeIndex = filename.lastIndexOf('.');
        if (typeIndex == -1)
          return filename;
        return filename.substring(0, typeIndex);
      }
    
      /***
       * Close the monkeyRecorder by closing the xml file and zipping it up with the screenshots.
       *
       * @param filename path of file to be stripped of type information
       */ 
      public static void close() throws IOException {
        // zip up xml file and screenshots into ROOT_DIR.
        byte[] buf = new byte[1024];
        String zipFileName = mXmlFilename + ".zip";
        endCommand();
        mXmlFile.close();
        FileOutputStream zipFile = new FileOutputStream(ROOT_DIR + "/" + zipFileName);
        ZipOutputStream ut = new ZipOutputStream(zipFile);
    
        // add the xml file
        addFileToZip(out, mDirname + "/" + mXmlFilename, buf);
        
        // Add the screenshots
        for (String filename : mScreenShotNames) {
          addFileToZip(out, mDirname + "/" + filename, buf);
        }
        out.close();
      }
      
      /***
       * Helper function to zip up a file into an open zip archive.
       *
       * @param zip the stream of the zip archive
       * @param filepath the filepath of the file to be added to the zip archive
       * @param buf storage place to stage reads of file before zipping
       */ 
      private static void addFileToZip(ZipOutputStream zip, String filepath, byte[] buf) throws IOException {
        FileInputStream in = new FileInputStream(filepath);
        zip.putNextEntry(new ZipEntry(filepath));
        int len;
        while ((len = in.read(buf)) > 0) {
          zip.write(buf, 0, len);
        }
        zip.closeEntry();
        in.close();
      }
    }
  • 有时候。。。why?

    2012-03-16 15:10:29

    有时侯
    觉得自己其实一无所有,仿佛被世界抛弃
    有时候
    明明自己身边很多朋友,却依然觉得孤单
    有时候
    走过熟悉的街角,看到熟悉的背影,突然就想起一个人的脸
    有时候
    突然很想哭,却难过的哭不出来
    有时候
    夜深人静的时候,突然觉得寂寞深入骨髓
    有时候
    突然找不到自己,把自己丢了
  • 自动化环境搭建

    2012-03-16 13:51:24

    说起自动化测试,我想大家都会有个疑问,要不要做自动化测试? 自动化测试给我们带来的收益是否会超出在建设时所投入的成本,这个嘛别说是我,即便是高手也很难回答,自动化测试的初衷是美好的,而测试工程师往往在实现 过程中花费了很多成本、精力,而最终以失败告终。 失败的原因会很多,我总结几项:

    1.    太过依赖测试工具,高估了工具的力量,最终会以失望告终。

    2.     项目紧急的情况,为了规避那些多余的环节,干脆人工测试,结果整个链路中断。

    3.     研发和测试人员不能很好的交互,如果这两个角色之间有了一道防火墙,那别说自动化测试了,手工测试也不会有好的效果。测试人员可以把研发想象成自己的女朋友, 努力培养相互之间的感情。

    4.     自动化测试人员实力被低估而导致的情绪低落,这个问题在所有岗位上都会发生,当然要看领导重视程度了。

    5.     资源不够,公司不舍得资源投入,或者蹑手蹑脚,那就不要浪费时间了。

    6.     太高的期望,过低的回报。

    现在国内没有几家公司说能玩转自动化测试的,在看国外那些耳熟能详的企业像google ,Microsoft ,它们反而搞得热火朝天的。可笑的是在微软中国的测试部门工作过几年的测试经理,出来搞个什么《微软自动化测试体系最佳实践课程》,就能忽悠国内的那些知 名的企业掏钱去听,我这也不是说他们的东西不好, 只是希望我们自己也能够重视起来,要知道人家“卓越”体系也是由那些“专业团队”花了很多的时间和精力才搭建起来的,所以先不说收益如何,即便是为了这一 个美好的初衷,我们也应该去尝试一下,即使是失败。

    我也做了几年的测试,但也是最近才开始研究自动化测试的,在工作中多多少少接触过几种常见的工具,如 silktest 和Selenium ,搞过测试的应该都听过,我最近刚刚为现有项目搭建了一个测试框架,为其中的部分模块编写了一些测试case, 收益还是颇丰的,先说说这两种工具吧。

    SilkTest 是商业工具,想使用是需要花钱的,但是有现成的平台和框架,也比较容易学习,可以基于windows ,unix 平台。ie ,firefox (部分)浏览器。可以录制回放,对象识别或者手工编程,也能基于数据驱动,关键字驱动等等测试框架。SilkTest 里面的语言4test 其实是类C (也有Silk4J ,用Java 写),编程能力相对较强。

    Selenium 是免费的,需要有类似ide (如eclipse ),原先是thoughtworks 的几个牛人开发的,现在google 维护(人都跳槽过去了)。可以基于windows ,unix ,mac 等平台。ie ,firefox 等浏览器。 测试团队,经费不足,能力很强(Java 上面),有对测试平台和环境要求比较高(ie ,firefox ,safari 等等),那么Selenium 是很不错的选择。

           今天我就简单入个门,先介绍通过junit+selenium+Coverlipse+ant 来搭建一整套自动化测试框架,开始之前我先提出几个问题,请大家根据下面的问题来阅读本篇文章。
    1. 如何录制脚本?

    2. 如何转换成junit 脚本?

    3. 如何生成junit 日志?

    4. 如何回放selenium 及查看回放日志?

    5. 如何查看代码的覆盖率?

    一、工具准备


    工具
     说明
     下载
     
    junit
     JUnit 是一个开发源代码的Java 测试框架,用于编写和运行可重复的测试。
     http://www.junit.org

     
     
    selenium
     先介绍两个重要的组件Selenium-IDE 来录制脚本;selenium-rc selenium-remote control 缩写,是使用具体的语言来编写测试类
     http://seleniumhq.org/download/

     
     
    Coverlipse
     Coverlipse 这个Eclipse 插件能够把JUnit 测试的代码覆盖直观化。
     http://coverlipse.sourceforge.net/download.php

     
     
    Ant
     Ant 是一个类似make 的工具, 大家都已经很熟悉了,这里我们可以利用其中的ant task 来生成junit 日志
     http://ant.apache.org/bindownload.cgi

     
     

     
    二、Junit 的安装

    1.         Eclipse 本身会自带Junit.jar 包,所一般情况下是不需要额外下载junit 的。

    2.         将junit3 的library 添加到工程的buildPath 中

    3.         因为junit 的日志是通过Ant 来生成的,所以一定要将Junit.jar 添加到ant_home 里

    三、selenium 的安装

    1.         安装seleniumIDE ,打开火狐浏览器,然后进入工具—> 添加附件,搜索seleniumIDE

    2.         查询出对应的IDE 后,点击直接安装,安装结束后重启FireFox ,看到下面的菜单说明安装成功

    3.         安装selenium-rc ,先去http://www.openqa.org/selenium/ 下载selenium 包。用命令行来到解压到文件夹下:d:\autoTesting\ selenium-server-standalone-2.0b1.jar 目录下

    4.         运行java -jar selenium-server-standalone-2.0b1.jar ,启动selenium server 。为了在运行时应用运行的浏览器与selenium 服务的浏览器分开可在其后面加–multiWindow 。

    5.         在Eclipse 创建一个项目,在项目的build path 里面加上elenium-server-1.0-beta-1 下selenium-server.jar 、selenium-java-client-driver-1.0-beta-1 下selenium-java-client-driver.jar (这两个在刚解压的包里面)和eclipse\plugins\org.junit_3.8.1 下junit.jar 。


    6.         将制定的Jar 包导入到工程里,然后你就可以集成并使用相应的API ,编写自己的测试CASE 了。

    四、Coverlipse 的安装

    1.         直接通过Eclipse 即可安装,步骤如下

    •In Eclipse, click Help -> Software Updates -> Find and Install.
    •In the dialog, select Search for new features to install, then Next.
    •In the next step, add a New Remote Site. Name it "Coverlipse update site", the URL is "http://coverlipse.sf.net/update/".
    •Press Finish. Eclipse now searches for the Coverlipse feature to install and shows that to you. 2.         配置 Coverlipse 以获取代码覆盖
     

    3.         一旦单击了 Run ,Eclipse 会运行 Coverlipse 并在源代码(如图 7 所示)中嵌入标记,该标记显示了具有相关 JUnit 测试的代码部分
    4.     Coverlipse 生成的具有嵌入类标记的报告


    5.      正如您所见,使用 Coverlipse Eclipse 插件可以更快地确定代码覆盖率。例如,这种实时数据功能有助于在将代码签入 CM 系统前 更好地进行测试。

    五、ANT 安装,eclipse 自带,只需要配置环境变量ant_home 即可。


    六、创建一个案例

    1.         创建一个工程testSelenium 安装下面目录结构

    2.         录制脚本,打开Firefox 浏览器,进入selenium IDE 菜单

    3.         输入相应录制的地址,点击红色按钮,开始录制

    4.         将脚本转换成junit 代码,然后将其拷贝到测试类中做为测试CASE 编码的雏形。

    六、如何查看日志,这里日志分两类:

    l          Junit 日志,通过junit 写的断言,和标准输出,这些操作产生的日志记录。

    l          Selenium 日志,当运行junit 脚本时,selenium 相关的脚本就会产生回放日志,例如打开界面的url ,标准输入,输出等信息。

        虽然这两种日志没有交集,需要分开查看。但一般情况下我们只需要观察Selenium 日志已经足够用了,与其相比Junit 日志更适用于编码阶段。

    1.       Junit 日志, 只需要配置脚本build-selenium.xml ,如下

    <project name="seleniumTest" default="junit" basedir=".">

           <property environment="env" />

           <condition property="ia.home" value="${env.IA_HOME}">

                  <isset property="env.IA_HOME" />

           </condition>

           <property name="run.classpath" value="../class">

           </property>

           <property name="run.srcpath" value="../testSelenium">

           </property>

           <property name="test.xml" value="../xml">

           </property>

           <property name="test.report" value="../report">

           </property>

           <property name="lib.dir" value="../lib" />

           <path id="compile.path">

                  <fileset dir="${lib.dir}">

                         <include name="junit.jar" />

                         <include name="ant.jar" />

                  </fileset>

           </path>

           <target name="init">

                  <delete dir="${run.classpath}" />

                  <mkdir dir="${run.classpath}" />

                  <delete dir="${test.report}" />

                  <mkdir dir="${test.report}" />

                  <delete dir="${test.xml}" />

                  <mkdir dir="${test.xml}" />

           </target>

           <target name="compile" depends="init">

                  <javac destdir="${run.classpath}" srcdir="${run.srcpath}" />

           </target>

           <target name="junit" depends="compile">

                  <junit printsummary="false">

                         <classpath path="${run.classpath}">

                                <path refid="compile.path" />

                         </classpath>

                         <formatter type="xml" />

                         <batchtest todir="${test.xml}">

                                <fileset dir="${run.classpath}">

                                       <include name="**/Test*.class" />

                                       <include name="**/*Test.class" />

                                </fileset>

                         </batchtest>

                  </junit>

                  <junitreport todir="${test.xml}">

                         <fileset dir="${test.xml}">

                                <include name="TEST-*.xml" />

                         </fileset>

                         <report format="frames" todir="${test.report}" />

                  </junitreport>

           </target>

    </project>

    2.       运行ant 脚本以后,就可以生成相应的junit 日志。

     
    1.selenium 日志
    当运行junit 脚本时,selenium 相关的脚本就会产生回放日志,但默认记录的东西可读性太差了,所以我们使用loggingSelenium ( http://loggingselenium.sourceforge.net/usage.html ) ,可以将每个case 可以生成记录selenium 命令的html 格式的result 了。

    4.       plugin 的下载地址:

    http://sourceforge.net/projects/loggingselenium/files/loggingselenium/Release%201.2/logging-selenium-1.2.jar/download
    5.       安装方法:只需要将下载的logging-selenium-1.2.jar 导入到工程里即可。

    6.       编写代码如下

    @Before

    public void setUp() {

        final String resultPath = "absolute-path-to-where-your-result-will-be-written";

        final String resultHtmlFileName = resultPath + File.separator + "result.html";

        final String resultEncoding = "UTF-8"

        loggingWriter = LoggingUtils.createWriter(resultHtmlFileName, resultEncoding);

     

        LoggingResultsFormatter htmlFormatter =

            new HtmlResultFormatter(loggingWriter, resultEncoding);

        htmlFormatter.setScreenShotBaseUri(""); // this is for linking to the screenshots

        htmlFormatter.setAutomaticScreenshotPath(resultPath);

        // wrap HttpCommandProcessor from remote-control

        LoggingCommandProcessor myProcessor =

             new LoggingCommandProcessor(new HttpCommandProcessor(your-configs), htmlFormatter);

        selenium = new LoggingDefaultSelenium(myProcessor);

        selenium.start();

    }

    @After

    public void tearDown() {

        selenium.stop();

        try {

            if (null != loggingWriter) {

                loggingWriter.close();

            }

        } catch (IOException e) {

            // do nothing

        }

    }

    7.       运行成功以后在指定的目录中生成相应的reports

       七

    七、框架优势
    1.       记录测试的过程,所见即是所得,selenium 的所有内部程序都是用Javascipt 编写的,比较灵活;

    2.       可以通过selenium IDE 录制脚本,脚本可以回放,可以作为junit 编码的雏形;

    3.       支持多种操作系统;

    4.       支持多种编码语言。JAVA,.NET, Perl, Python, Ruby

    八、框架劣势

    1.      selenium 的录制工具只能安装在firefox 浏览器上, 如果系统界面不被firefox 支持,那就要花费一定的时间去手写case 。 不过最近听说有一个工具 叫 360WebTester , 可以支持IE 的录制,而且是国产的评价还不错,有时间我要研究一下。

  • 【转】Selenium自动化工具工作原理

    2012-03-16 11:20:14

    原文作者信息如下,需要更多信息请去原作者博客查看:

    作者:hyddd
    出处:http://www.cnblogs.com/hyddd/
    本文版权归作者所有,欢迎转载,演绎或用于商业目的,但是必须说明本文出处(包含链接)。


    ==============================================================================================


    Selenium简介》 中讲过,Selenium RC支持多种语言编写测试案例,如:C#,Python。在工作中,我倾向于是用Python这类动态语言编写测试案例,因为这样的测试案例无需编 译:>,试想如果你有1000个测试案例,每个都要编译,那会给编译服务器很大的压力,而且案例修改后,还得重新编译才能运行:<。但在本系 列的文章中,我还是打算使用C#编写示范例子。

    Selenium RC下载:http://seleniumhq.org/download/

    写Selenium RC的测试案例

         上一篇《Selenium IDE的使用》中,提到了Selenium IDE可以把录制的脚本转为其他语言的脚本,所以我继续用上一篇的脚本为例子,下面是把脚本语言转换为C#后的代码:

    1. using System;  
    2. using System.Text;  
    3. using System.Text.RegularExpressions;  
    4. using System.Threading;  
    5. using NUnit.Framework;  
    6. using Selenium;  
    7.   
    8. namespace SeleniumTests  
    9. {  
    10.     [TestFixture]  
    11.     public class NewTest  
    12.     {  
    13.         private ISelenium selenium;  
    14.         private StringBuilder verificationErrors;  
    15.           
    16.         [SetUp]  
    17.         public void SetupTest()  
    18.         {  
    19.             selenium = new DefaultSelenium("localhost", 4444, "*chrome""http://change-this-to-the-site-you-are-testing/");  
    20.             selenium.Start();  
    21.             verificationErrors = new StringBuilder();  
    22.         }  
    23.           
    24.         [TearDown]  
    25.         public void TeardownTest()  
    26.         {  
    27.             try  
    28.             {  
    29.                 selenium.Stop();  
    30.             }  
    31.             catch (Exception)  
    32.             {  
    33.                 // Ignore errors if unable to close the browser  
    34.             }  
    35.             Assert.AreEqual("", verificationErrors.ToString());  
    36.         }  
    37.           
    38.         [Test]  
    39.         public void TheNewTest()  
    40.         {  
    41.             selenium.Open("/");  
    42.             selenium.Type("kw""hyddd");  
    43.             selenium.Click("sb");  
    44.             selenium.WaitForPageToLoad("30000");  
    45.             try  
    46.             {  
    47.                 Assert.IsTrue(selenium.IsTextPresent("hyddd - 博客园"));  
    48.             }  
    49.             catch (AssertionException e)  
    50.             {  
    51.                 verificationErrors.Append(e.Message);  
    52.             }  
    53.             selenium.Click("//table[@id='1']/tbody/tr/td/a/font");  
    54.         }  
    55.     }  
    56. }  



    在这里,转换后的脚本使用了NUnit测试框架,为了简化,我用VS的Test Project代替(当然你也可以用Console Application建立测试工程的),步骤如下:
    1.建立Test Project

    2.导入DLL引用

        把selenium-dotnet-client-driver-1.0-beta-2目录中的ThoughtWorks.Selenium.Core.dllThoughtWorks.Selenium.IntegrationTests.dllThoughtWorks.Selenium.UnitTests.dll加入项目:

    3.把上面自动生成的代码再改一下

    1. using System;  
    2. using System.Text;  
    3. using System.Collections.Generic;  
    4. using Microsoft.VisualStudio.TestTools.UnitTesting;  
    5. using Selenium;  
    6.   
    7. namespace SeleniumTest  
    8. {  
    9.     [TestClass]  
    10.     public class UnitTest1  
    11.     {  
    12.         [TestMethod]  
    13.         public void Test()  
    14.         {  
    15.             //127.0.0.1为Selenium测试服务器位置。  
    16.             //4444为Selenium测试服务器监听端口。  
    17.             //*iexplore为启动浏览器类型,我把它改为了IE浏览器。  
    18.             //http://www.baidu.com为源地址。  
    19.             ISelenium selenium = new DefaultSelenium("127.0.0.1", 4444, "*iexplore""http://www.baidu.com");  
    20.             selenium.Start();  
    21.             selenium.Open("/");  
    22.             selenium.Type("kw""hyddd");  
    23.             selenium.Click("sb");  
    24.             selenium.WaitForPageToLoad("30000");  
    25.             Assert.IsTrue(selenium.IsTextPresent("hyddd - 博客园"));  
    26.             selenium.Click("//table[@id='1']/tbody/tr/td/a/font");  
    27.       selenium.Close();  
    28.             selenium.Stop();  
    29.         }  
    30.     }  
    31. }  



    4.启动Selenium测试服务器
        打开cmd进入selenium-server-1.0-beta-2目录,输入“java -jar selenium-server.jar”
    (需要先安装JRE),启动Selenium测试服务器。

    5.运行测试案例
    (1).运行测试案例:

    (2).测试结果:


    恩,案例Pass了,如果案例失败的话,Error Meesage会说明失败的原因。
    (注意:和Firefox一样,IE下也有屏蔽弹出网页功能,修改设置方法:MenuBar->Tools->Popup Blocker->Turn off Popup Blocker,或者在Popup Blocker Settings里面配置。)




       前一篇已经比较详细讲述了如何使用Selenium RC进行Web测试,但到底Selenium RC是什么?或者它由哪几部分组成呢??

    一.Selenium RC的组成:

    关于这个问题,我拿了官网上的一幅图来说明这个问题。

     

    Selenium RC主要由两部分组成:

    (1).Selenium Server:

     

    Selenium Server负责控制浏览器行为,总的来说,Selenium Server主要包括3个部分:LauncherHttp ProxySelenium Core。其中Selenium Core是被Selenium Server嵌入到浏览器页面中的。其实Selenium Core就是一堆JS函数的集合,就是通过这些JS函数,我们才可以实现用程序对浏览器进行操作。

    (2).Client Libraries:

    写测试案例时用来控制Selenium Server的库。

     

    二.Selenium RC与Testcase的关系

    先看下图:

     

    (1).测试案例(Testcase)通过Client Lib的接口向Selenium Server发送Http请求,要求和Selenium Server建立连接。

    为什么要通过发送Http请求控制Selenium Server而不采用其他方式呢?从上文可以看出,Selenium Server是一个独立的中间服务器(确切地说是代理服务器),它可以架设在其他机器上!所以测试案例通过发送HTTP请求去控制Selenium Server是很正常的。

    (2).Selenium Server的Launcher启动浏览器,把Selenium Core加载入浏览器页面当中,并把浏览器的代理设置为Selenium Server的Http Proxy。

    (3).测试案例通过Client Lib的接口向Selenium Server发送Http请求,Selenium Server对请求进行解析,然后通过Http Proxy发送JS命令通知Selenium Core执行操作浏览器的动作。

    (4).Selenium Core接收到指令后,执行操作。

    (5).浏览器收到新的页面请求信息(因为在(4)中,Selenium Core的操作可能引发新的页面请求),于是发送Http请求,请求新的Web页面。
    由于Selenium Server在启动浏览器时做了手脚,所以Selenium Server会接收到所有由它启动的浏览器发送的请求。

    (6).Selenium Server接收到浏览器的发送的Http请求后,自己重组Http请求,获取对应的Web页面。

    (7).Selenium Server的Http Proxy把接收的Web页面返回给浏览器。

    为什么Selenium RC中的Selenium Server需要以这种代理服务器的形式存在呢?下一篇继续介绍:>


    继续前一篇的问题,为什么Selenium RC中的Selenium Server需要以这种代理服务器的形式存在?其实,这和浏览器的“同源策略”(The Same Origin Policy)有关。

    一.什么是同源策略

        同源策略,它是由Netscape提出的一个著名的安全策略,现在所有的可支持javascript的浏览器都会使用这个策略。

    为什么需要同源策略,这里举个例子:
        假设现在没有同源策略,会发生什么事情呢?大家知道,JavaScript可以做很多东西,比如:读取/修改网页中某个值。恩,你现在打开了浏览器,在一 个tab窗口中打开了银行网站,在另外一个tab窗口中打开了一个恶意网站,而那个恶意网站挂了一个的专门修改银行信息的JavaScript,当你访问 这个恶意网站并且执行它JavaScript时,你的银行页面就会被这个JavaScript修改,后果会非常严重!而同源策略就为了防止这种事情发生, 看下图:

     

    比如说,浏览器的两个tab页中分别打开http://www.baidu.com/index.html和http://www.google.com/index.html,其中,JavaScript1和JavaScript3是属于百度的脚本,而JavaScript2是属于谷歌的脚本,当浏览器的tab1要运行一个脚本时,便会进行同源检查,只有和www.baidu.com源的脚本才能被执行,所谓同源,就是指域名、协议、端口相同。所以,tab1只能执行JavaScript1和JavaScript3脚本,而JavaScript2不能执行,从而防止其他网页对本网页的非法篡改。


    二.Selenium Server为什么以这种代理服务器的形式存在
        上面说了同源策略,那同源策略的Selenium Server有什么关系呢??呵呵,上一篇说过,Selenium Core是一堆JS函数的集合,它是我们操作浏览器的基础。当存在同源策略时,便出现一些问题,看下图:

    因为Selenium Core的JS脚本的“源”是localhost,所以浏览器会阻止Selenium Core的JS脚本在测试页面上执行,这就是为什么在本系列第一篇中说,如果只使用Selenium Core进行测试,需要把Selenium Core安装到远程服务器上。

        为了解决上面这个问题,Selenium RC中的Selenium Server就以代理服务器的形式出现了,下图说明它是如何借助代理的身份蒙骗浏览器的:>

    Selenium Server以代理的形式存在,通过修改WebSite的源信息,从而达到欺骗浏览器的目的,就这样,Selenium RC就轻松绕过了同源策略。在上图中,浏览器会认为WebSite和Selenium Core来自同一个“源”----代理服务器!

  • QTP_Tutorial

    2012-02-20 16:46:18

    QTP_Tutorial,QTP帮助
  • monkeyrunner脚本生成器

    2012-01-11 10:02:54

    monkeyrunner脚本生成器

  • 测试基本教程

    2011-12-16 18:16:47

  • web测试1

    2011-12-16 18:11:43

  • (转)翻页功能测试用例设计(详细)

    2011-12-16 18:09:23

    翻页功能我们常碰到的一般有以下几个功能:

      1、首页、上一页、下一页、尾页。
      2、总页数,当前页数
      3、指定跳转页
      4、指定每页显示条数

      当然,有一些是少于多少页,全部以数字的形式显示,多于多少页后,才出现下一页的控件。本文暂且用以上四点来做为通用的用例来设计吧。

      对于1翻页链接或按钮的测试,主要要检查的测试点有:

      1、有无数据时控件的显示情况
      2、在首页时,首页和上一页是否能点击
      3、在尾页时,下一页和尾页是否能点击
      4、在非首页和非尾页时,四个按钮功能是否正确
      5、翻页后,列表中的记录是否仍按照指定的排序列进行了排序

      对于2总页数,当前页数,主要要检查的测试点有:

      1、总页数是否等于总的记录数/指定每页条数
      2、当前页数是否正确

      对于3指定跳转页,主要要检查的测试点有:

      1、是否能正常跳转到指定的页数
      2、输入的跳转页数非法时的处理

      对于4指定每页显示条数,主要要检查的测试点有:

      1、是否有默认的指定每页显示条数
      2、指定每页的条数后,列表显示的记录数,页数是否正确
      3、输入的每页条数非法时的处理

      分析完上面的测试点,应该可以进行用例的设计了。

      step 1: 列表无记录
      expect: 1、四个翻页控件变灰不可点击
      2、列表有相应的无数据信息提示
      3、不可指定页数
      4、不可指定跳转页
      5、总页数显示为0
      6、当前页数显示为0

      step 2: 列表的记录数<=指定的每页显示条数
      expect: 1、四个翻页控件变灰不可点击
      2、总页数显示为1
      3、当前页数显示为1

      step 3: 列表的记录数>指定的每页显示条数
      expect: 1、默认在首页,当前页数为1
      2、列表的数据按照指定的排序列正确排序
      3、记录数与数据库相符
      4、总页数=记录数/指定的每页显示条数

      step 4: 列表的记录数>指定的每页显示条数,在首页
      expect: 1、首页变灰不可点击
      2、上一页变灰不可点击
      3、下一页可点击,从(每页指定条数+1)条记录开始显示,当前页数+1
      4、尾页可点击,显示最后页的记录

      step 5: 列表的记录数>指定的每页显示条数,在中间的某页
      expect: 1、首页可点击,显示1到每页指定条数的记录
      2、上一页可点击,显示上一页的记录
      3、下一页可点击,从后一页的记录
      4、尾页可点击,显示最后页的记录
      5、列表的数据按照指定的排序列正确排序
      6、当前页数为所在页

      step 6:列表的记录数>指定的每页显示条数,在尾页
      expect: 1、首页可点击,显示1到每页指定条数的记录
      2、上一页可点击,显示上一页的记录
      3、下一页变灰不可点击
      4、尾页变灰不可点击
      5、列表的数据按照指定的排序列正确排序
      6、当前页数为最后一页的页数

      step 7:输入每页显示条数为正整数
      expect: 1、每页显示条数更新成指定的条数
      2、超过指定的条数的记录分页显示
      3、总页数更新成列表的记录数/每页显示条数

      step 8:输入每页显示条数为0
      expect: 1、提示“每页显示条数必须为大于1的整数”
      2、提示后每页显示条数恢复为上次生效的条数

      step 9:输入每页显示条数为负数
      expect: 1、提示每页显示条数必须为大于1的整数
      2、提示后每页显示条数恢复为上次生效的条数

      step 10:输入每页显示条数长度超过数据库指定的长度<<<maxlen>>>
      expect: 1、提示每页显示条数不能超过<<<maxlen>>>位
      2、提示后每页显示条数恢复为上次生效的条数

      step 11:输入每页显示条数为字符串,如中文翻页数
      expect: 1、提示每页显示条数必须为大于1的整数
      2、提示后每页显示条数恢复为上次生效的条数

      step 12:输入每页显示条数为特殊字符,如%
      expect: 1、提示每页显示条数必须为大于1的整数
      2、提示后每页显示条数恢复为上次生效的条数

      step 13:输入每页显示条数为html字符串,如<br>
      expect: 1、提示每页显示条数必须为大于1的整数
      2、提示后每页显示条数恢复为上次生效的条数

      step 14:输入跳转的页数为存在的页数
      expect: 1、正确跳转到指定的页数

      step 15:输入跳转的页数不存在或非法值
      expect: 1、跳转的页数值置为1,显示第一页的数据

      以上的用例是将总页数,当前页数都揉进了翻页控件的测试用例中了。

  • window下简单监控iPhone手机上软件cup等占用情况

    2011-11-24 09:48:40

    在window下监控iPhone手机上软件资源利用情况步骤如下:
    1,在iPhone手机上安装Cydia(一般越狱后都有);
    2,进入软件搜索OpenSSH和OpenSSL和top 安装;
    3,安装完成后,进入设置,打开开发者;
    做完以上这些后,设置iPhone无线链接,查看无线ip

    然后在window下可以通过SecureCRT进行连接



    输入用户名root
    密码:alpine
    可以看到连接成功:



    在输入top,可以看到相关软件的一些cup占用等信息(如图QQ的)

  • monkeyrunner测试apk包安装卸载

    2011-04-21 18:54:51

    from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice, MonkeyImage
    device = MonkeyRunner.waitForConnection()
    #device.removePackage ('ApiDemos.apk')
    device.removePackage ('com.example.android.notepad')
    print ('卸载成功')
    MonkeyRunner.sleep(15)
    device.installPackage('ApiDemos.apk')
    print ('安装成功')
  • 了解monkey命令行的一些参数

    2011-04-21 18:47:34

     

    1.-p:所在测试的包,可以是一个也可以是多个(如:monkey -p com.androd.a -p com.androd.b)

    2.-c:如果用此参数指定了一个或几个类别,Monkey将只允许系统启动被这些类别中的某个类别列出的Activity。如果不指定任何类别,Monkey将选择下列类别中列出的Activity: Intent.CATEGORY_LAUNCHER或Intent.CATEGORY_MONKEY。要指定多个类别,需要使用多个-c选项,每个-c选项只能用于一个类别.

    3.-ignore-crash:当应用程序崩溃或发生任何失控异常时,Monkey将停止运行。如果设置此选项,Monkey将继续向系统发送事件,直到计数完成.

    4.-ignore-timeouts:通常,当应用程序发生任何超时错误(如“Application Not Responding”对话框)时,Monkey将停止运行。如果设置此选项,Monkey将继续向系统发送事件,直到计数完成.

    5.-ignore-security-exceptions:通常,当应用程序发生许可错误(如启动一个需要某些许可的Activity)时,Monkey将停止运行。如果设置了此选项,Monkey将继续向系统发送事件,直到计数完成

    6.-monitor-native-crashes:监视并报告Android系统中本地代码的崩溃事件。如果设置了–kill-process-after-error,系统将停止运行

    7.-kill-process-after-error:如果程序出现错误,monkey将结束此程序进程

    8.-hprof:设置此项,将在monkey事件序列之前和之后立即生成profilling报告。这将会在data/misc中生成大文件(约5mb)所以要小心使用它

    9.-pct-touch:调整触摸事件的百分比(触摸事件是一个down-up事件,它发生在屏幕的某单一位置)

    10.-pct-motion:动作事件的百分比(动作事件由屏幕上某处的一个down事件、一系列的随机事件和一个up事件组成)

    11.-pct-trackball:调整轨迹事件的百分比(轨迹事件由一个或几个随机移动组成,有时还伴随着点击)

    12.-pct-syskeys:调整系统按键事件的百分比(这些按键通常被保留,由系统使用,如home,back,start call,end call及音量控制)

    13.-pct-nav 调整基本导航事件的百分比(导航事件来自方向输入设备的up/down/left/right组成)

    14.-pct-majornav:调整“主要”导航事件的百分比(这些导航事件通常引发图形界面中的动作,如:5-way键盘的中间按键、回退按键、菜单按键)

    15.-pct-appswitch:调整启动Activity的百分比。在随机间隔里,Monkey将执行一个startActivity()调用,作为最大程度覆盖包中全部Activity的一种方法

    16.-pct-anyevent:调整启动Activity的百分比。它包罗了所有其它的事件类型,如:按键,其它不常用的设备按钮

    17.–wait-dbg:停止执行中的Monkey,直到有调试器和它相连接

    18.–dbg-no-events:设置此选项,Monkey将执行初始启动,进入到一个测试Activity,然后不会再进一步生成事件。为了得到最佳结果,把它与-v、一个或几个包约束、以及一个保持Monkey运行30秒或更长时间的非零值联合起来,从而提供一个环境,可以监视应用程序所调用的包之间的转换

    19.-port:为monkey开启专用端口。此时只monkey不会帮你乱点击,而此时你自己就是一只monkey了,在你乱点的时候,monkey会输出你点击后回馈的信息。如果你打完命令之后模拟器上没有启动你所要启动的包,你需要自己启动,但是你只能启动你-p中指定的那几个包。ctrl+c中断

    20.--throttle :当事件起效时等待的毫秒数

    21.-s:随机数生成器的seed值。如果用相同的seed值再次运行monkey,它将生成相同的事件序列

    22.COUNT:要发送的事件数

     

  • android的monkey测试小结

    2011-04-21 17:39:29

    Monkey测试是Android自动化测试的一种手段,Monkey测试本身非常简单,就是模拟用户的按键输入,触摸屏输入,手势输入等,看设备多长时间会出异常。

    Monkey程序在模拟器或设备运行的时候,如果用户出发了比如点击,触摸,手势或一些系统级别的事件的时候,它就会产生随机脉冲,所以可以用Monkey用随机重复的方法去做相关的性能测试。

    命令:$ adb shell monkey -v -p your.package.name 500

    例如:monkey -v -p com.android.camera --throttle 5000 --pct-anyevent 100 500

    -v显示默认程度的信息

    -p com.android.camera是指定测试的程序(这是开始测试的camera的内容

    --throttle 5000 设定延时

    --pct-anyevent 100(设定启动activity的百分比为100%

    500设定的事件数

    (具体可以看monkey的命令帮助)

    以下是个例子: monkey -p com.example.android.notepad -v -v -v 10

    运行结果相关解释如下:

    各种事件所占的比例

    // Event percentages:
    //   0: 15.0%
    //   1: 10.0%
    //   2: 15.0%
    //   3: 25.0%
    //   4: 15.0%
    //   5: 2.0%
    //   6: 2.0%
    //   7: 1.0%
    //   8: 15.0%

    表示跳转到com.example.android.notepad里面的NotesList这一个Activity

    :Switch: #Intent;action=android.intent.action.MAIN;category=android.intent.categ
    ory.LAUNCHER;launchFlags=0x10000000;component=com.example.android.notepad/.Notes
    List;end

    允许此Intent跳转

    Allowing start of Intent { act=android.intent.action.MAIN cat=[android.in
    tent.category.LAUNCHER] cmp=com.example.android.notepad/.NotesList } in package
    com.example.android.notepad

    发送的一些动作:

    Sleeping for 0 milliseconds
    :SendKey (ACTION_DOWN): 21    // KEYCODE_DPAD_LEFT
    :SendKey (ACTION_UP): 21    // KEYCODE_DPAD_LEFT
    Sleeping for 0 milliseconds
    :Sending Pointer ACTION_MOVE x=-4.0 y=2.0
    :Sending Pointer ACTION_MOVE x=-5.0 y=-4.0
    :Sending Pointer ACTION_MOVE x=0.0 y=-1.0
    :Sending Pointer ACTION_MOVE x=-3.0 y=2.0
    :Sending Pointer ACTION_MOVE x=-4.0 y=2.0
    :Sending Pointer ACTION_MOVE x=-2.0 y=4.0
    :Sending Pointer ACTION_MOVE x=4.0 y=1.0
    Events injected: 10

    丢弃的,键=0,指针=0,轨迹球=0,翻转=0

    :Dropped: keys=0 pointers=0 trackballs=0 flips=0

    网络统计经过时间为7249ms,其中7249ms是用于在手机上的,0ms用于无线网络上,没有连接的时间为0ms

    ## Network stats: elapsed time=7249ms (7249ms mobile, 0ms wifi, 0ms not connecte
    d)

    结束// Monkey finished

  • 测试的基本概念(共享)

    2010-12-16 16:57:55

    测试的基本概念(共享)
341/212>
Open Toolbar