发布新日志

  • 测试三十六计-第一计-瞒天过海

    2013-05-26 09:43:49

    【释义】
    防备得周全时,更容易麻痹大意;习以为常的事,也常会失去警戒。秘密常潜藏在公开的事物里,并非存在于公开暴露的事物之外。公开暴露的事物发展到极端,就形成了最隐秘的潜藏状态。

    在我们测试过程中,漫天过海是很常用的一种计谋。

    很多错误,往往都是隐含在我们已经习惯了正确的区域,这也是为什么,我们在不停的在做让人觉得很枯燥无味和产出比很低的回归测试的原因。

    很多测试团队,都是习惯了使用已经存在的功能测试用例来进行回归测试,导致很多bug在被僵化的测试过程所遗漏。所以,进行更加有效的回归测试必须要去探索那些我们遗漏的阴影区域。而如何去探索这些区域呢,一般可以考虑的方法有如下几个:
    1.在功能测试完毕后,对bug进行分析,得出bug的分布图和原因。然后在没有发现bug,但是被主要bug趋势范围影响到的功能区域,加强回归测试的深度。
    2.在回归测试完毕后,对功能区域进行评价,对于发现问题较多的功能区域进行更加宽泛的广度优先的二次回归。
    3.定期的评审回归测试用例,对测试集合进行调整,不要只针对新功能直接影响的,而是那些在底层更迭模块所影响的应用层进行更多的关注。


    另外一个大范围使用瞒天过海的测试范畴就是安全性测试。

    在安全性测试中,我们大部分的方法就是设置一个虚假的信息,企图访问系统,从而给系统带来损害。

    比如我们使用jmeter去获得一个特定角色的访问过程,该角色的权限为低级。我们在测试时,人为的修改该角色的权限,通过jmeter发起请求,看是否能够成功的骗过登录策略进入系统。

    还有就是我们认为的修改数据库中的某条数据,然后进行下束流程,看系统是否在流程进行过程中对数据完整性进行有效的校验。

    总之,瞒天过海在测试领域里,就是要验证那些隐藏的流程和数据处理过程。这些流程和过程在我们用户前端是被屏蔽或者隐藏掉的。虽然正向的和反向的操作都不会出现问题。但是,除了正向和反向的操作这些已经被开发刻意处理过的范围之外,仍然会潜藏一些常规方法无法探知的缺陷。这些缺陷只有通过非常规的测试手段,主动的攻击才会被表露出来,从而获得更深层次的质量保障。
  • 一些常见的问题回答(1)

    2013-05-10 10:30:04

    Q1: 请问,测试用例该如何进行推广,现在很多的项目都能走正常的测试流程比如说,计划,方案,测试用例编写和审 ...

    这属于测试用例管理的通病,问题不在于使用什么工具,而是测试用例是如何被定位的。只是单纯的走流程,测试用例往往就变成了一个临时的过程文档。 想要把测试用例使用的更加有效,如下几个建议:
    1. 测试用例的编写必须基于有效的需求文档,并且是强耦合。也就是说,需求变化时,测试用例必须及时更新。
    2. 测试用例的审核,必须需求,开发,测试共同参与。审核的目标是测试用例的覆盖度。
    3. 测试用例的编写必须基于统一的标准,包括格式,深度,广度。
    4. 测试用例必须和task结合,也就是说在制定测试计划时,要根据不同task来设置不同的测试用例集合。
    5. 测试用例必须和bug强耦合。bug和测试用例的priority和severity是相辅相成的。
    6. 测试用例必须是测试报告的基础,日报,周报,月报,项目阶段报告,里程碑报告等报告的三大支柱就是:测试用例状态,bug状态和schedule状态。
    7. 测试用例必须根据测试实际情况选取,priority和severity是选取测试用例集的重要指标。
    8. 测试用例集要分多个层次,所有的测试过程所用的测试用例都是主测试用例集的子集。

    Q2:请问嘉宾,面对需求不明确,工期短的项目:
    1.怎样做好测试需求分析;
    把use case和user story弄好,一旦所有参与人员都on same page了,需求就明确了。

    2.面对团队参差不齐的水平成员怎样 ...
    任何团队都是由各个不同背景的人组成的,只有一时的最短板,没有永远的最短板。team leader的作用就是根据每个team member的个人能力安排task。

    q3:在敏捷开发中,测试如何找做好高效率迭代测试的工作?

    敏捷测试跟传统测试最大的不同就是:
    1. 敏捷测试是质量驱动,传统测试是质量保障
    2. 敏捷测试是发起者,传统测试是验收者
    3. 敏捷测试追求的是最快反应用户的真实需求,传统测试是保证已有需求的完整性。
    4. 敏捷测试的测试对象是user story,传统测试的测试对象是单一功能
    5. 敏捷测试的过程是反复迭代累加的动态过程,传统测试的过程是预先设定的静态过程。

    所以,在敏捷测试中,一定要把传统测试中那种保证功能有效的想法抛弃,真正的敏捷,不在于功能是否正确,而在于是否真实的反应需求。所以一定要把use case和user story两者搞清楚。学会如何基于user story来完成迭代测试,如何使用user story来拼接use case来保证回归的质量。

    q4:请教现在想到的几个问题:
    1、如何做好测试需求分析?
    2、在根本就没有文档的情况,甚至连原型都没得看,上来直接就丢给你一个东西,要测试的情况下,如何保证测试质量?
    3、实际工作中,设计测试用例时真的会按那些设计方法去一步一步设计吗?

    测试需求分析其实很简单,就是从几个方面来考虑:
    1. 从功能方面。该需求实现的功能是什么。
    2. 从权限方面。该需求的用户组是什么。
    3. 从流程方面。该需求的上下文和驱动方式,以及流程方向。
    4. 从影响方面。该需求的对已有功能的依赖性。
    5. 从过程方面。该需求产生的各个过程结果,以及导向。

    问题2其实很常见,尤其你中途加入一个新的产品测试过程时。其实个上面的一样,你根据已有的资料先逐步分析问题1中各项。将clear的标识出来,试着写出user story,画出use case和各种流程图,然后大家做到一起进行确认,同时将unclear的作为question list在讨论过程中逐一提出。

    测试用例的设计其实无所谓使用哪种方法,那些都是一些工具而已,一旦你熟练的掌握了那些技巧,在你实际设计时都会自然而然的使用上,无剑胜有剑。

    q5问题:
    1. 针对测试执行过程中,如何让测试进度以及质量更加透明(针对开发和测试),有没有什么注意点(ps:不要太官方的回答,如及时反馈等等,呵呵)
    2. 针对老年化的测试测试团队,如何提高大家的测试积极性和测试热情?

    测试进度和质量更透明? 呃,如果是敏捷开发团队的话,一般都会有个例会,每个QA都会将自己测试的进度,发现的问题,以及问题的分析在例会上讲出来。相关的Dev和关联的QA都会得到反馈。 

    如果不是的话,那就每周发一个周报,给所有的开发和测试,包含以下几个方面就好了:
    1. 测试的进度。各个task测试进度,然后可以放几个对比图,比如速度啊,failed率啊啥的。
    2. bug的分析。 各种图,对应task也好,功能也好, 反应该块的质量。
    3. 综合分析,针对以上进行分析,列出low efficiency的原因也好,弄个risk list也好,总之让大家知道我们team的风险状态

    然后,让dev也回发一个,针对QA的分析从开发角度进行回答,例如代码的重构引起什么样的问题过多出现,提醒QA多做某种类型的回归测试等等。

    我不知道啥叫老年化,不过你说的应该是混日子的老资格太多了吧。一般这个出现很正常,惰性嘛,都有。 无非就是从几个方面激励一下:
    1. 精神上。比如,让老QA们搞一些training文档给新人。
    2. 物质上。该涨工资的涨,该升level的生,做不到的话,给个title也行啊,人很容易满足的。
    3. 技术上。引入一些新技术,新的测试方法。
    4. 团队上。没事组织大家多活动,交流,工作是同事,生活是朋友。
    5. 影响上。有些实在混的厉害的,就该罚就罚,开除个太混的至少顶半年用。
  • 从零开始学JMeter(1)

    2013-04-22 17:41:50

    什么是JMeter

    Apache JMeter是一个开源的Java桌面软件。设计的目的就是进行C/S架构软件的负载测试。随着发展,有很多人也用来进行一些静态资源或者动态资源的性能测试。可以支持的测试对象有:静态文件,Java Servlets, CGI scripts,Java 对象,数据库,FTP服务器等等。

    JMeter的原理就是模拟产生一个大数据压力包,然后使用不同的负载测试方法去研究server,object或者network的健壮性以及分析在过载情况下的性能指标。

    当然,对于一些简单的workflow,你也可以使用JMeter来建立一套带断言的脚本来进行复杂度较低的回归测试。

    有一点要注意的是,JMeter本身不是一个Browser,一定要理解这一点。


    一些技术细节我们就不在这里讨论了,下载后就可以很简单的通过bat文件将其运行起来,至于安装java环境,那就不在详细讨论了。

    下面,先来理解一些标准术语,方便我们后面进行讲解。

    Test Plan

    Test Plan,是用来集合需要执行的步骤的一个节点,基本上所有的操作,都是在Test plan里存在的。在Test plan之外的步骤,不会被执行。

    你可以将Test plan理解为一个要实现的测试用例,一个包好了若干测试用例的user story,或者是一个特殊的测试scenario,甚至可以当作一个test suite。完全取决于你在这个Test plan中包含了什么。

    包含在Test  plan中的节点,我们称之为element。所有的Elements组成了不同结构的Tree存在于Test plan这个root之下。

    你可以在其中任意的添加或者删除element或者一个elements tree。

    Element

    让我们来看看,有哪些element可以被使用。

    ThreadGroup

    对于每个Test Plan来说,第一个节点永远是ThreadGroup。所有的Controller 元素
    和Sampler 元素必须位于ThreadGroup元素中。

    ThreadGroup最重要的作用是用来配置一下三个参数:
    Thread的数量
    Thread的ramp-up时间
    执行该Test Plan的次数。

    Thread的数量我们很好理解,就是每一个线程模拟一个用户,执行该test plan所有的步骤。数量代表用户模拟数。但是,有一点要注意,从单server上发起的多用户模拟和多server上发起的多用户模拟需要在客户端进行处理。有些server会把单server上所有的请求当作一个用户的请求,而且无法实现多用户并发的模拟。

    Ramp-up period是一个需要仔细考虑的设置。它的含义是在指定的period内把所有的Thread创建完毕。例如,有100个Thread,这个period设置为0,那么就是瞬间并发100个Thread。如果设置为2秒,那么就是在两秒内依次把所有的Thread产生。理论上是每隔2/100=0.02秒一个请求。

    所以,如果我们在模拟真实的用户请求是,你要有一个预先的response极限,比如0.01秒/response是极限的话,过低的时间频段,只会带来大量无效的请求,更适合over load情况下的性能测试,而不是需要逐级加压的load test的要求。

    另外有一个Think Time也值得关注,Think Time是指的思考时间,是一个很明确的时间间隔,Ramp-up period的时间间隔只是理论上的,而Think Time是指定的时间间隔。在模拟多用户并发操作时,我们还要考虑在不同时间节点上的负载平衡。通过将Think Time 和 Ramp-up period相结合,才能更加贴近真实的用户负载场景。

    Controllers

    JMeter实际上包含了两种Controller元素:Sampler元素和Logic Controller元素。通过二者的组合,从而实现具体的测试内容。

    Sampler元素用来定义哪些请求要被发送到server。而且Logic Controller元素则控制那个Sampler元素在什么时候被发送。

    Sampler

    JMeter现有的Sampler元素有:
    FTP请求
    HTTP请求
    JDBC请求
    Java Object请求
    LDAP请求
    SOAP/XML-RPC请求
    Web Service(SOAP)请求

    每个元素里面都有一些相关的属性需要配置来完成请求发送的任务。同时为了更好的控制业务层次,还提供了很多Configure元素来提供给这些Sampler元素进行调用,从而更加完善的控制流程。

    同时可以给Sampler元素配置上一个Listener元素用来记录请求返回的结果。当你想对这些返回的结果进行一个验证是,再添加一个Assertion元素即可。

    Logic Controllers

    对于逻辑控制,那是必不可少的,可以将我们单一的线性步骤列表,变成一个可控制的多层次的复用模块的组合。

    提供可以使用的Logic Controller元素有:
    Simple Controller 用于组合sampler和其他logic Controller
    Loop Controller 用于控制loop循环
    Once Only Controller 用于控制仅一次的操作,例如login
    Interleave Controller 交错控制,使得包含的步骤交错执行在每个循环中。
    Random Controller  随机控制,随机选择包含的步骤之一进行执行
    Random Order Controller 随机顺序控制,以随机的顺序执行所有的步骤
    Throughput Controller 执行频度控制,用于控制执行步骤的范围和量。
    Runtime Controller 生命周期控制器,用于控制可生存的时间长度
    If Controller 控制if操作
    While Controller 控制while操作
    Switch Controller 控制switch操作
    ForEach Controller 遍历操作控制,用来便利当前元素的所有可执行场景
    Module Controller  用于控制封装好的Module元素
    Include Controller 用于引入外部的jmx文件,从而控制多个Test Plan组合。
    Transaction Controller 控制如何产生额外的时间测量sampler。
    Recording Controller 控制录制使用的代理服务器的地址

    我们会在后面详细的介绍每个controller的具体使用方法。
    Test Fragments

    该元素是一个特殊的Controller元素,是后来引入的。特点是可以和ThreadGroup处于并列级别的存在于Test Plan的树中。而一般的Controller元素都要位于ThreadGroup的节点之内。 

    只有当其被Module Controller元素或者Include Controller元素引用后,才会被执行。

    Listeners

    Listener元素用于提供Jmeter在运行中产生的信息。产生的这些信息,其实就是我们需要的数据报告。也就是我们测试要获得的结果报告。

    Listener元素可以产生数据文件报告,图形报告,也可以是直接存储为各种文件格式的报告。

    Timers

    JMeter在ThreadGroup中产生的Thread都是没有任何停顿的,哪怕是设置了产生间隔,也仍然是连续的去产生Thread,每个Thread之间没有任何停顿。如果你想要在每个Thread间进行停顿,需要设置Timer元素。当然,Timer主要还是用于Sampler去控制产生的压力,从而避免过压的产生,从而得不到有效的数据。

    Assertions

    当你需要对server返回的数据进行一些分析和判断是,需要使用的就是Assertion元素。这在大压力的情况下很有必要,因为有可能返回速度达到了你的要求,但是很多系统在无法response时,也会返回一条回应。通过Assertion元素,我们才能清楚的知道成功率是多少。

    当然,如果你是来构建一套回归测试suites,那么Assertion元素是必不可少的。

    Configuration Elements

    Configuration element是为Sampler元素服务的。他们不会直接的参与请求的发送过程,但是发送的内容是通过Configuration 元素和Sampler元素的内容组合而成的。该元素只能被同一个父节点的兄弟节点以及子节点所使用,父节点的兄弟节点无法使用。

    Pre-Processor Elements

    该元素主要是在Sampler元素发送请求之前,进行一些预处理操作使用。
    Post-Processor Elements

    该元素主要是在Sampler元素发送请求之后,进行一些收尾处理操作使用。最常用的实例就是在获得response的data时候,从data中提取我们想要的特定数据。

    Common Execution Order

    对于所有element的执行顺序,一般来说是这样的:

    Configuration element
    Pre-Processor
    Timer
    Sampler
    Post——Porcessor
    Assertion
    Listener

    (我以为listener会在Assertion前面呢)

    Properties and Variables

    JMeter的properties在文件jmeter.properties中定义。 对于JMeter来说,这是全局共享数据。主要是用来定义一些常用的默认值。在Test Plan中,可以覆盖一些默认的properties。

    而JMeter的variables是服务于每个Thread的局部变量。每个Thread使用的variables都是不同的,完全取决它们的自己需要。

    而且需要注意的是,Thread在执行的时候是复制了一份variables的副本,修改也是基于副本修改,不会影响被多个Thread共享的原本。

    而且variables支持正则表达式。

    对于在Test Plan和User Define Variables Configuration Element中定义的variables,是可以被包含在该Test Plan中的所有步骤共享。并且可以使用Set方法去设置新的值,从而使所有的步骤都获得更新。
    使用variables最大好处就是可以数据参数化。Variables本身是作为一个储存一个可被共享的常量来设计的。但是,它的本质还是一个可变的数据引用对象。

    对于一个循环中,或者多个Test Plan中的一个Test Plan,实际上它等同于一个常量。但是,当多个循环和多个Test Plan存在时,我们要操作的数据对象是相同的,但是数据对象的值可以根据场景进行变化,这就为什么要对将那些最常用的数据对象进行参数化。

    举个简单的例子帮助你更好的理解:

    比如我们要循环访问一个站点的不同页面, 去查看一个特定的字符串是否存在。那么我们可以将站点地址参数化,从而在不同的站点的Test Plan中使用同一个variable,只需要修改它的值即可。同时,我们在每个page中check的特定字符可以是相同的,也可以根据不同的页面不同。那么将这个特殊字符参数化以后,我们可以用相同的Module来执行所有的重复操作。


  • 实例解析AUTOMATION TEST FRAMEWORK (2)

    2013-03-25 10:29:57

    WATIR, WEB DRIVER,CUCUMBER测试框架分析和建立产品框架的过程

    Watir是一个Ruby库,可以利用多版本浏览器进行Web应用程序测试。

    Web Driver是一个由Google团队创建的简洁快速的框架,用于Web应用程序的自动化测试。

    而Watir Web Driver就是将二者合二为一形成一个新的测试框架。

    Cucumber是一个能够理解用普通语言描述的测试用例的支持行为驱动开发(BDD)的自动化测试工具。

    我们可以通过Cucumber + Watir Web Driver 实现一个功能更加强大的BDD测试框架。

    Cucumber框架的目的很简单:

    由自然语言书写的Feature文件>>通过Define Step进行匹配>>调用相匹配的Watir Web Driver实现的自动化测试脚本。

    所以,我们的主要工作就在于如何使用Watir Web Driver进行自动化脚本的实现。

    首先要知道Web Driver是一个独立的framework,并没有依赖于任何现有的framework,所以在跟其他framework集成时,不需要做过多的屏蔽工作。

    Web Driver的目的,就是作为一个简单有效的工具,来进行web application的自动化测试。它提供了能够直接被其他框架使用的API。

    所以,Web Driver的作用就是简化了对Browser操作的封装。它已经包含了足够使用的方法,使得你在创建自己的框架时,可以无需再提供额外的Browser操作的封装模块。

    Watir Web Driver,等于是将Watir和Web Driver融合,使用Watir来进行页面元素,操作,数据控制等模块,使用Web Driver来达到在Browser上对这些元素进行控制的目的。

    而我们在项目实际应用中,需要根据具体情况再次封装我们的产品,从而实现特定产品的test framework。


    对于web driver,我们无需理会,它等于封装了底层的对browser的操作。我们需要了解的是Watir 里,如何对页面元素进行控制。

    一般来说,对于一个web页面进行自动化测试的框架,无非需要实现以下几个功能模块:

    页面元素的查找
    页面元素的操作
    页面元素的判断
    页面元素的获取
    页面元素的断言
    错误处理
    数据驱动
    日志管理
    执行管理


    我们可以看出,这个framework的核心就是围绕着element进行的。

    对于Watir Web Driver Framework来说,element的所有控制都是基于该元素的tag进行分类的,这就保证了它对任何语言和任何浏览器能够进行操控,只要该产品的页面是 遵循HTML标准的。

    我们来看看,它能支持的element对象有哪些:

    Area - <area>
    Button - <input type=”button”>, <input type=“image”>, <input type=”reset”>, <input type=”submit”>
    Cell - <td>
    Checkbox - <input type=”checkbox”>
    Div - <div>
    File_field - <input type=”file”>
    Form. - <form>
    Frame. - <frame>, <iframe>
    H1, H2, H3, H4, H5, H6 - <h1>, <h2>, <h3>, <h4>, <h5>, <h6>
    Hidden - <input type=”hidden”>
    Image - <img>
    Label - <label>
    Li - <li>
    Link - <a>
    Map - <map>
    P - <p>

    Pre - <pre>
    Radio - <input type=”radio”>
    Row - <tr>
    Select_list - <select>
    Span - <Span>
    Table - <table>
    Text_field - <input type=”password”>, <input type =”text”>, <textarea>
    Ul - <ul>

    从上面的列表,我们看到,基本上HTML元素都被涵盖在内了。这样,我们在封装我们自己的方法时,可以使用完全OO的思想,来封装我们要进行的操作。

    作为一种纯粹OO的语言,ruby和纯粹的OO的自动化框架Watir,它提供的element的通用方法有如下:

    == 是否相等
    attribute_value 返回指定属性的值
    add_checker 添加checker
    browser 返回所在browser对象
    click 左键单击
    clear 清除选择
    close 关闭
    double_click 左键双击
    drag_and_drop_by 拖拽到指定区域
    drag_and_drop_on 拖拽到指定元素上
    driver 返回所用的driver
    enabled? 是否可以点击
    exists? 是否存在
    fire_event 执行指定的事件
    flash 高亮该对象
    focus 焦点移至该对象上
    focused? 焦点是否在对象上
    hash 返回该对象的hash值
    hover 鼠标悬浮在该对象上
    html 返回该对象的html内容
    inspect 返回该对象的inspect值
    parent 返回该对象的父节点
    present? 是否存在并可见
    right_click 右键点击一次
    run_checkers 返回正在运行的对象列表
    style 返回对象的style值
    set 设置值
    send_keys 发送值参
    tag_name 返回对象的tag值
    text 返回对象的text值 
    to_subtype 返回对象的子类型
    type 返回对象的类型
    value 返回对象的value值
    visible? 是否可见
    wd 返回该对象的driver值
    wait_until_present 对象出现前等待
    waith_while_present 对象消失前等待
    when_present 对象出现前等待

    这些方法是基本所有element都通用的方法。之所以这些方法没有封装到一个方法类总库,就是因为watir要实现的纯粹OO的框架,所以,所有的操作都是基于实例化的element上,而不是element再继承方法抽象类来实现自己的方法。

    这点和Java的理念有所不同。Java 中, 对象是对象,方法是对象的属相,而在基于ruby的Watir中,对象是对象,方法也是对象,所有的一切都是封装好的对象。


    当然,每个element都还有一些私有的方法,满足各自的不同需求。我们也可以根据这些element,来组合一个新的element来重载或者继承多种方法,来满足一些特殊情况的需要。

    比如,我们完全可以把Login action 封装成一个login对象。该Login对象是通过user name 和password 这两个子元素,以及login button元素组合形成的业务模块。

    对于user name和password都分别 属于一个text_field 元素,而 login button属于一个button元素。我们把它们封装成为一个login元素。

    当我们查找这个元素时,需要同时匹配user name,password和login button三个属性值,然后调用内置的方法set和click 形成一个新的方法login action。

    通过上面的例子,我们对基于业务模块或者商业逻辑如何封装产品有了个初步的了解。

    下面我们要了解最终的核心部分,那就是如何获取页面的元素。

    获取页面元素,其实就是如何能够定位该页面元素,而定位的方法,就是根据该元素的一些属性信息进行查找。这些属性信息有哪些呢,让我们来一起看一下。


    因为watir是使用标准的html tag来作为对象来进行识别的,所以这些元素的属性信息,基本上和html中每个tag元素具有的属性相同,这种方式就大大减少了我们学习新封装方法的工作量。

    我们来看看,每个tag都有哪些属性可以用来进行定位:

    Area - <area> - :class, :id, :index, :name, :text
    Button – <button> - :alt, :class, :id, :index, :name, :text, :value
    - <input type=”button”> - :alt, :class, :id, :index, :name, :text, :value
    - <input type=“image”>  - :alt, :class, :id, :index, :name, :src, :text, :value
    - <input type=”reset”> - :alt, :class, :id, :index, :name, :text, :value
    - <input type=”submit”> - :alt, :class, :id, :index, :name, :text, :value
    Cell - <td> - :class, :id, :index, :name, :text
    Checkbox - <input type=”checkbox”> - :class, :id, :index, :name, :text, :value
    Div - <div> - :class, :id, :index, :name, :text
    File_field - <input type=”file”> - :class, :id, :index, :name, :title, :value
    Form. - <form> - :action, :class, :id, :index, :method, :name
    Frame. - <frame>, <iframe> - :id, :index, :name, :src, :text
    H1, H2, H3, H4, H5, H6 -    <h1> - :class, :id, :index, :name, :text
    - <h2> - :class, :id, :index, :name, :text
    - <h3> - :class, :id, :index, :name, :text
    - <h4> - :class, :id, :index, :name, :text
    - <h5> - :class, :id, :index, :name, :text
    - <h6> - :class, :id, :index, :name, :text
    Hidden - <input type=”hidden”> -:class, :id, :index, :method, :name, :text, :value
    Image - <img> - :alt, :class, :for, :id, :index, :name, :src, :text, :value
    Label - <label> - :class, :id, :index, :name, :text
    Li - <li> - :class, :id, :index, :name, :text
    Link - <a> - :after?, :class, :href, :id, :index, :name, :text
    Map - <map> - :class, :id, :index, :name, :text, :value
    P - <p> - :class, :id, :index, :name, :text
    Pre - <pre> - :class, :id, :index, :name, :text
    Radio - <input type=”radio”> - :class, :id, :index, :name, :text, :value
    Row - <tr> - :class, :id, :index, :name, :text
    Select_list - <select> -:class, :id, :index, :name, :text, :value
    Span - <Span> - :class, :id, :index, :name, :text
    Table - <table> - :class, :id, :index, :name, :text
    Text_field - <input type=”password”> - :class, :id, :index, :name, :text, :value
                                     - <input type =”text”> - :class, :id, :index, :name, :text, :value
                                     - <textarea> - :class, :id, :index, :name, :text, :value
    Ul - <ul> - :class, :id, :index, :name, :text


    我们用一些具体的例子来更好的理解这些。

    对于一个具有class,id,index,name,和text属性的area,我们可以按如下方法查找:

    Browser.area(:class,  “area class name”)
    Browser.area(:id,  “area id”)
    Browser.area(:index, 0)
    Browser.area(:name, “area name”)
    Browser.area(:text, “area text”)

    我们也可以任意选取其中几个属性来更精确的定位:

    Browser.area(:class=>“area class name”, :id=>“area id”, :index, 0)

    另外,我们为了更好的,或者说更无奈的方法,是采用Xpath定位。Xpath定位很强大,但是经常会出现多个match对象时返回并非需要的对象,并且造成代码过于复杂的识别,影响执行速度,所以Xpath定位要少用。

    Browser.area(:xpath, “//area1”)


    Waitr直接采用了ruby本身的File来处理Data-driven的行为。

    我们来看一个操作Excel文档的实例:

    require 'win32ole'
    #支持excel文件操作

    require 'watir-webdriver'
    #watir web driver支持

    #打开数据文件
    excel = WIN32OLE::new('excel.Application')
    workbook = excel.Workbooks.Open('d:\rubycode\test.xlsx')

    #激活指定的tab
    worksheet = workbook.Worksheets('test')
    worksheet.Activate

    #读入用户名和密码信息
    @username = worksheet.Range('a1').Value
    @password = worksheet.Range('b1').Value
    #关闭文件
    excel.Quit

    #使用Watir webdriver进行自动化测试
    #设置Base URL
    $BASE_URL=http://10.32.148.243:8080

    #生成webdriver的实例
    $browser=Watir::Browser.new:ie

    #转向指定的URL
    $browser.goto($BASE_URL+'/parkinglot/login')

    #判断登录界面出现
    Watir::Wait.until {$browser.text.include?('停车场管理系统登录')} 

    #写入从数据文件中得出的测试数据
    $browser.text_field(:name, "username").set(@username)
    $browser.text_field(:name, "password").set('1234')

    #执行登录
    $browser.div(:class, "login_btn").click

    #确认正确登录到主页
    Watir::Wait.until {$browser.text.include?('Copyright')}


    从上例可知,watir webdriver 只是提供了如何获得element的方法。文件操作的方法,都是直接调用了已有的Ruby on Rails框架中的方法。 而对浏览器的操作,也是调用的Web Driver提供的方法。 

    这在我们做测试框架中很常见,我们可以再次体会到,框架就是用来被引用,或者被其他框架所包含,从而提供更丰富的API接口这种理念。

    现在我们已经理清了这套框架的基本架构:

    Ruby on Rails框架,提供给了Watir Web Driver框架的底层应用,例如文件的操作
    Web Driver框架,提供给了Watir Web Driver框架对Browse进行操作的方法
    Watir Web Driver框架本身提供了对页面元素进行识别,操作的方法
    Cucumber框架则提供了一个可以用自然语言去调用Watir Web Driver框架的方法
    好了,明白了这一切,我们就可以动手做自己的Automation Test Framework了。当然,一般来说,选用这些现成的框架即可。

    我们真正需要做的,就是如何封装自己的产品。封装的方式无外乎这几种:

    基于Page的封装
    基于流程的封装
    基于业务逻辑的封装
    基于功能模块的封装
    基于行为的封装
    基于数据模型的封装

    一般来说,怎么封装都可以,每一种封装方式都有自己的优缺点,所以在封装不同的东西选择不同的封装方式很常见。比如上面提到的例子,login的封装,你可以理解为基于Page的封装,也可以看作是行为的封装,甚至是流程的封装等等,你的着眼点不同,它的意义也就不同。

    一般来说,封装的目的就是形成三层结构。现有的框架我们可以认为是底部驱动层。而我们依据产品的封装可以看成是逻辑层,而我们测试用例就是所谓的应用层。

    而Cucumber + Watir Web Driver也可以进行任意的封装,但是最好的封装方式,是基于对象的方式。为什么这么说呢,我们通过实际的例子来看一下。

    Cucumber执行自动化测试用例,是通过feature文件来实现的,我们拿一个feature文件来看看。

    # language: zh-CN
    功能:区域管理
      作为一个管理人员
      我能够进行触摸屏管理

      场景: Case 1 - 添加触摸屏-确定(S2-ST-JBXXGL-CMPGL-0001)
        假如进入触摸屏管理界面
        而且按下新增按键
        而且在触摸屏名称里输入ATT
        而且从所属楼层列表中选择地下一层
        而且在触摸屏IP地址里输入188.0.0.1
        而且在触摸屏X坐标里输入88
        而且在触摸屏Y坐标里输入99
        而且按下触摸屏确定按键
        当判断预期结果时
        那么可以看到ATT
      场景: Case 2 - 添加触摸屏-取消(S2-ST-JBXXGL-CMPGL-0002)
        假如进入触摸屏管理界面
        而且按下新增按键
        而且在触摸屏名称里输入Touch
        而且从所属楼层列表中选择地下一层
        而且在触摸屏IP地址里输入188.0.0.1
        而且在触摸屏X坐标里输入88
        而且在触摸屏Y坐标里输入99
        而且按下触摸屏取消按键
        当判断预期结果时
        那么看不到Touch

      场景: Case 3 - 修改触摸屏-取消(S2-ST-JBXXGL-CMPGL-0003)
        假如进入触摸屏管理界面
        而且触摸屏选择值为ATT
        而且按下修改按键
        而且在触摸屏名称里输入A
        而且按下触摸屏取消按键
        当判断预期结果时
        那么看不到ATTA

      场景: Case 4 - 修改触摸屏-确定(S2-ST-JBXXGL-CMPGL-0004)
        假如进入触摸屏管理界面
        而且触摸屏选择值为ATT
        而且按下修改按键
        而且在触摸屏名称里输入A
        而且按下触摸屏修改按键
        当判断预期结果时
        那么可以看到ATTA

      场景: Case 5 - 删除触摸屏-取消(S2-ST-JBXXGL-CMPGL-0005)
        假如进入触摸屏管理界面
        而且触摸屏选择值为ATTA
        而且按下删除按键
        而且按下确定取消按键
        当判断预期结果时
        那么可以看到ATTA





      场景: Case 6 - 删除触摸屏-确定(S2-ST-JBXXGL-CMPGL-0006)
        假如进入触摸屏管理界面
        而且触摸屏选择值为ATTA
        而且按下删除按键
        而且按下确定删除按键
        当判断预期结果时
        那么看不到ATTA


    我们可以看到,对于user story:

    功能:区域管理
      作为一个管理人员
      我能够进行触摸屏管理

    我们一共有6个场景来保证该story已经能够被完成,至于有bug在内,那不是判断story是否可以被认定为完成的依据,那是交付的依据。


    我们随便取其中的一个场景来分析:

      场景: Case 5 - 删除触摸屏-取消(S2-ST-JBXXGL-CMPGL-0005)
        假如进入触摸屏管理界面
        而且触摸屏选择值为ATTA
        而且按下删除按键
        而且按下确定取消按键
        当判断预期结果时
        那么可以看到ATTA

    第一行,我们可以认为是一个navigation的过程,那么所有的navigation操作,可以作为一个通用的流程进行封装。那么封装的方式就可以是:

    Given /进入(.*)界面/ do |module_name|
      $autotest.navigate_to_module(module_name)
    end  

    从上面可以看出,它是根据要转向的module name,或者是页面名称来进行转向的,是一个纯粹的业务流程的封装。

    但是,我们也可以基于Page去封装,那么又如何去书写语句呢?


    我们可以把每个Page都当作一个对象,那么我们就把“触摸屏管理界面”封装成一个名叫touchscreen的page对象,那么上面的语句就可以写成:

    Given /进入*界面/ do |module_name|
    if module_name ==”  触摸屏管理” then
    $autotest.touchscreen.visit();
    end
    end  

    我们可以看到,这么封装的方式好处是page上所有的操作都可以封装到一个page对象中,当该页面发生跟新时,我们只需要更新该page对象即可,这在以前是很流行的封装方式。

    但是如果考虑到这种情况呢:

    页面里面有个element,比如search button,是被很多页面共同使用的。在基于page的封装方法中,你不得不在每一个包含该search button的页面中进行封装。一旦该元素进行了修改,那么意味着你需要更新所有的page对象。 在这个过程中,很容易犯错。

    所以cucumber推崇的是基于最基本的页面元素的封装。例如button的封装:

    #按下xx按键
    Given /按下(.*)按键/ do |button_name|
      $autotest.click_button(button_name)   
    end

    它是封装了button的click事件。这样所有的button的click都可以调用这一个统一的方法,我们也可以完全基于某个element做属性封装,例如:

    #按下xx按键
    Given /(..)(.*)按键/ do |button_action, button_name|
      $autotest.button(button_name, button_action)   
    end

    而在button中封装了一个方法就是Button.action(button_action), 这里key就是button_action=click。从而触发这个click事件。

    所以说,那种封装方式不重要,重要的是怎么能减少你后期维护的时间和精力,以及如何更大限度的复用代码,这两者必须达到平衡,不能偏向任何一边。

  • 实例解析AUTOMATION TEST FRAMEWORK (1)

    2013-03-25 10:27:47

    什么是Automation Test Framework

     

    自动化测试框架,是多个基础测试模块,管理模块,统计模块,执行模块等组成的可复用的底层模块集合。

     

    举个实际的例子,Selenium本身就是一个Framework。我们基于Selenium对我们要测试的产品进行二次封装,例如基于page,或者基于element,就形成了个新的Framework。然后我们基于这个新的Framework,去开发一些复用的模块,方法等等。然后基于这些复用的方法和模块,我们去开发测试用例。

     

    这听起来,很像是软件开发中常见的三层结构之类的感觉。不错,所谓的Automation Test Framework,其实很多时候,就是基于现有的一些Framework,比如SeleniumWatir来控制底层操作,我们自己二次开发的代码是基于产品的业务逻辑进行封装,然后实现自动化测试用例。

     

    所以,框架的意思,就是你能搭建一个能让上一层依赖你而且满足上层需求的底层驱动集。很多公司是直接使用现成的测试框架进行开发测试用例,有些公司会根据具体的业务需求扩展现在有的测试框剪来满足需要。

     

    所以,测试框架应该是个动态的框架,当你只是使用它时,它只是个静态工具而已。

     

    框架的组成部分

     

    我们拿Test Studio提供的免费的开源框架来做个实例,它包含了一下几个主要的部分:

     

    ·         Automation Infrastructure library

    ·         Internet Explorer client

    ·         Firefox client

    ·         Silverlight plug-in

    ·         HTTP Proxy

    ·         ASP.NET TestRegion Custom Control.

     

    其中,Automation Infrastructure Library 就是最底层的支撑框架,我们如何去识别element,如何操作element都依赖于此,如同一个程序语言的核心库。

     

     

    而且它自带了一些自我定制的client,用于对指定的浏览器进行操作。这些操作,实际上是封装了操作系统自带的方法。所以,该软件只能在基于Net Framework的操作系统上运行。同时提供了一个Plug-in用于对Silverlight进行支持。

     

    HTTP Proxy的作用有两个,一个是在录制的时候作为监听端口的控制,从而将需要的控制信息录制下来。在回放的时候,作为监听去收集log信息。

     

    从以上我们可知,框架的组成部分,都是你在测试中需要处理的行为,比如怎么获得浏览器,怎么进行浏览器操作,怎么进行录制,怎么进行log记录等等。

     

    框架的核心

     

    对于一个Web自动化测试框架来说,最核心的部分是要提供如下的功能:

     

    ·         浏览器的控制

    ·         页面元素对象的的识别

    ·         页面元素对象的搜索

    ·         页面元素对象的操作

    ·         进行断言

    ·         Dom的构建

    ·         处理错误信息

    ·         记录日志

     

    让我们看看,Test Studio的测试框架是怎么样实现的。

    Manager.ActiveBrowser 


    在其中封装的对浏览器的操作,这些操作包含了:

    Browser.Actions

    Browser.Find

    Browser.DomTree

    Browser.Window


    其中,Browser.Actions提供了如下几类方法:


    Browser Control – 用于对Browser进行控制,从而是的browser能够按照我们需要的行为进行操作。这些操作是基于Browser对象进行的。每一个新打开的窗口,都代表了一个Browser对象,我们可以对对象进行具体的操作,例如:转向指定的URL,返回上一个page,等待,刷新等等。



    DOM automation – 用于对基于DOM存在的元素进行操作。所有的DOM操作,都是使用一个Action对象进行控制。这些Action对象是在一个Browser对象创建时,基于DOM对象生成的。举一个具体的Action对象的例子,比如Set Text,本身是一个Action,即所谓的行为,我们把这个行为当作一个对象来进行操作,那么我们就可以实现对于个Text Field的element对象来执行一个Action对象。Element是要操作的对象,而Action对象包含的是对这个element进行操作的操作内容和数据。


    Pure UI automation – 这里面包含的是纯粹的那些GUI的操作,例如鼠标点击,和键盘输入。这些操作作为“Desktop”存在。例如Simulate方法,就是要进行set Focus on,action和data enter的操作集合。


    我们来看一些具体的实例来帮助我们理解:


    对Browser的操作


    ActiveBrowser.NavigateTo("http://www.google.com");

    当前页面转向URL:http://www.google.com


    当存在base URL时,可以直接使用间接地址:

    ActiveBrowser.NavigateTo("~/videosearch");


    可以对Browser本身的属性进行操作,例如验证browser的Title:

    string pageTitle = ActiveBrowser.PageTitle;

    Assert.AreEqual("Video Search Page", pageTitle, "Actual page title = \"{0}\"", pageTitle);


    在鼠标点击事件后,browser等待操作完成

    Desktop.Mouse.Click(MouseClickType.LeftClick, 300, 275);

    ActiveBrowser.WaitUntilReady();


    对element的操作


    查找一个id为input1的文本框对象

    Element input1 = ActiveBrowser.Find.ById("input1");   

      

    查找一个id为textarea1的文本域对象

    Element textArea = ActiveBrowser.Find.ById("textarea1");    

      

    查找一个id为inputradio的Radio button对象

    Element inputradio = ActiveBrowser.Find.ById("inputradio");    

      


    查找一个id为inputcheck的checkbox对象

    Element inputcheck = ActiveBrowser.Find.ById("inputcheck");    

      

    查找一个id为selection的下拉框对象

    Element selection = ActiveBrowser.Find.ById("selection");


    对DOM的操作


    在对象input1里面输入字符串“test1”

    ActiveBrowser.Actions.SetText(input1, "test1");    

      

    选择radio button对象inputradio  

    ActiveBrowser.Actions.Check(inputradio, true);    

      

    钩取checkbox对象inputcheck

    ActiveBrowser.Actions.Check(inputcheck, true);

        

    在下拉框列表selection中选择序号为4的选项

    ActiveBrowser.Actions.SelectDropDown(selection, 4);    

      

    在下拉框列表selection中选择文本为“one1”的选项

    ActiveBrowser.Actions.SelectDropDown(selection, "one1");    

      

    在下拉框列表selection中选择值为“test”的选项

    ActiveBrowser.Actions.SelectDropDown(selection, "test", true);    

      


    而在Browser.Find中有存在哪些操作呢,主要是如何根据不同的信息进行element的定位,可以使用的方式有如下几种:


    Find.ById()

    Find.ByName()

    Find.ByTagIndex()

    Find.ByAtrributes()

    Find.AllByAttributes()

    Find.ByContent

    Find.AllByContent()

    Find.ByXPath

    Find.AllByXPath()

    Find.AllByTagName()

    Find.ByNodeIndexPath()

    Find.ByParam()

    Find.AllByParam()

    Find.FromCollection()

    Find.ByCustom()

    Find.AllByCustom()


    以上的方式,基本上可以保证找到所需的element对象。我们来举几个例子:


    页面上有个元素具有id属性,id为input1

    Element e = Find.ByID(“input1”)


    页面上有个元素具有name属性,name为searchbtn

    Element e = Find.ByName(“searchbtn”)


    页面上有多个table,那么我们可以根据序号选择需要的表,比如第2个

    Element e= Find.ByTagIndex(“table”, 2)


    如果想使用多个属性值寻找

    Element e = Find.ByAttributes(“class=inputclass”, “name=input1”)


    如果有多个匹配对象返回,那么返回的是第一个

    Element e= Find.ByAttributes(“class=myclass”)


    如果想返回所有的匹配对象,那么可以这么操作

    List <Element> e=Find.AllByAttributes(“class=myclass”)


    如果想根据全文匹配查找一个字符串,那么是l来标识

    Element e = Find.byContent(l:Education)


    如果想根据部分匹配查找一个字符串,那么是p来标识

    Element e = Find.byContent(p:Education)


    如果想根据正则表达式来匹配一个字符串

    Element e = Find.ByContent(@"x:^(<tr>\s*<td\s*scope=.*>\s*Education)");


    如果想返回所有匹配的对象,可以使用部分匹配

    IList<Element> alle = Find.AllByContent("p:car");


    以上的例子就是为了说明Test Studio是如何对Browser的操作进行封装的,基本上采用的方式是以Data-Driven方式进行的。更加关心的是通过数据来获得对象。


    那么让我们看看Action是怎么进行Keyword-Driven的封装。



    Manager.ActiveBrowser.Actions


    在其中封装的对数据对象的操作,这些操作包含了:


    Actions.Click

    Actions.SetText 

    Actions.Check

    Actions.WaitForElement


    这些操作,是依据你要进行的处理进行封装。更加在意的是有哪些操作,而不在意是那些数据要进行操作。这个封装方式,更像是将ActiveBrowser,也就是Browser对象进行方法封装,凡是在Browser对象中封装的element都可以使用这些Action方法。


    Manager.ActiveBrowser.Find


    这个模块,是为了满足在Browser对象中封装的DOM列表中进行查找的操作。它封装了一个很重要的功能Find.SearchRegion。 他和Action对象一样,是对Browser封装的有效补充。Action是被Browser对象中的DOM所筛选出来的element,在页面上查找匹配对象后,该对象进行的操作。而Find起到的作用就是在页面上查找匹配对象,是通过实际的页面来反向查找DOM的过程。


    Manager.ActiveBrowser.DomTree


    既然所有的操作实际上都是因为Browser封装了DOM树实现的,那么需要一些方法进行DOM树的操作,之所以把这些方法放到Browser之外进行封装,那是因为DOM树的创建和更新是在进行录制过程中实现的。所以,不需要很臃肿的包含在主包中。


    它包含的两个主要内容是TreeBuilder.TestRegion's和 TreeBuilder.Root。一个是用来获取DOM树,就是从root节点load的过程。另外一个就是创建树的builder。


    Manager.ActiveBrowser.Window


    那么Browser是存在于哪里呢,是在一个Window窗口中,所以有时候也需要对这些容器进行操作,我们也把相关的操作封装起来。



    主要的方法有两个:Window.Handler用来对窗口进行处理, 而 Window.GetBitmap()为了存照给log使用。要知道Browser是不存在状态的,真正具有各种状态的是Windows,尤其是在切换不同的Browser的实例时,切换的对象是Window本身,而不是这个容器里的Browser对象。



    Manager.ActiveBrowser.Frames



    Frame这个东西挺讨厌的,类似于在Browser里面搞了个分裂活动,很多测试框架都不得不专门对它进行封装操作。



    Manager.ActiveBrowser.Regions


    这个封装的是要返回的具体对象,总不能老拿庞大的Browser对象到处跑。封装的方法就是Count, Item, Items这几个比较重要的对象。实际上,有点像把一个立体的DOM树平铺到二维数组中,这样的话才便于操作。



    Manager.Desktop


    剩下的是Desktop对象,实际上就是封装了鼠标和键盘的事件。


    Manager.Log


    而log中封装的就是日志操作,这个就不用说了。让我们回头来再回顾一下:


    Manager.ActiveBrowser

    Manager.ActiveBrowser.Actions

    Manager.ActiveBrowser.Find

    Manager.ActiveBrowser.DomTree

    Manager.ActiveBrowser.Window

    Manager.ActiveBrowser. Frames

    Manager.ActiveBrowser. Regions

    Manager.Desktop

    Manager.Log


    从上面的说明可知,Web Automation的操作,就是对页面上的元素进行操作,这些元素都被封装在ActiveBrowse的DOM树中。


    而Actions中封装就是这些元素对象的共用方法,Find中封装的是如何在DOM树中查找,DOMTree封装的是对DOM树的操作。而Regions封装的是DOM树的组织方式和映射方式。 


    Window是封装的容器,Frames是特殊的控制,不得不封装在外。


    所以所有的核心都是以包围ActiveBrowser所实例化的Browser对象进行的。


    所以我们在使用该测试架构时,可以按照如下方式进行:


    根据页面逻辑或者业务逻辑,封装Page对象,这些对象是作为Browser对象的实例存在,该page中所有的html element都存储到该Browser对象的DOM树中。这样就根据page来封装了所有需要操作的element对象。


    然后根据业务逻辑或者业务流程,调用page中封装的element对象,实现一些可以复用的业务逻辑或者方法。


    然后根据Data-driven或者Keyword-driven的方式去实现自动化测试用例。


    从而以一个三层结构的方式完成了测试框架的搭建。从而能够减少因为需求更新导致的代码更新的工作量和减少平行依赖性带来的风险。


    从上面过程可以看出,现有的test framework实际上都是封装好的半成品,将底层构建好以后,能够让你迅速的基于底层框架搭建上层框架,从而建立真正适用于你要测试产品的框架,或者集成到已有的测试框架中。甚至可以被更高级的开放框架所引入,达到丰富其他框架的目的,例如robot+selenium形成一个新的测试框架。


  • Ruby 小结

    2013-03-18 16:18:16

    Ruby的变量没有类型
    所有变量均无需声明即可立即使用
    不需要内存管理
    纯粹的面向对象语言
    功能强大的字符串操作和正则表达式检索功能

    字符串是指被单引号(')或双引号(")括起来的部分
    在用双引号括起来的字符串中,可以使用反斜杠(\)来指定转义字符
    用[]括起来的一串表达式是数组,而以{}括起来的一串表达式是散列表

    局部变量,变量名以小写英文字母开始。
    全局变量,以$开始
    实例变量,以@开始
    类变量,以@@开始
    类常数,以大写英文字母开始

    赋值 变量=表达式
    调用方法 表达式.方法名
    使用分号(;)或换行来分隔表达式

    流程控制结构:
    if 表达式 then 代码块 [elsif 表达式 then 代码块]..[else 代码块]end
    case 表达式 when 表达式..; 代码块...[else 代码块] end
    while 表达式;代码块 end
    for 变量..in 表达式; 代码块 end
    break 中断循环
    next 开始下一次循环
    redo 重新执行块的第一行

    exit([status]) 结束程序的运行
    gets 从命令行参数指定的文件(群)中读取一行,然后把该行的内容作为字符串返回。
    open(文件名[,mode]) 打开文件
    print 输出函数

    正则表达式

    [ ] 范围描述符。[a-z]表示从a到z之间的任意一个。
    \w 英文字母和数字。即[0-9 A-Z a-z]。
    \W 非英文字母和数字
    \s 空字符,即[\t\n\r\f]。
    \S 非空字符。
    \d 数字,即[0-9]。
    \D 非数字。
    \b 词边界字符(在范围描述符外部时)
    \B 非词边界字符
    \b 退格符(0x08)(在范围描述符内部时)
    * 前面元素出现0次以上
    + 前面元素出现1次以上
    {m,n} 前面元素最少出现m次,最多出现n次
    ? 前面元素出现0次或1次
    | 选择
    ( ) 群组
    其他字符 该字符本身
  • Ruby语言入门(25)- 内部类 - Time

    2013-03-18 15:43:47

    时间对象. Time.now返回当前时间. 由File#stat所返回的文件的时间标记正是Time对象.

    Time对象中保存的是自起算时间以来所经过的秒数. 起算时间定在协调世界时(UTC,或旧称GMT)的1970年1月1日上午0点。系统内部保存了一个标识,用来确定Time对象是采用协调世界时的时区还是采用地方时的时区:

    p Marshal.load(Marshal.dump(Time.now.gmtime)).zone
    =>"UTC"

    Time.at(time[, usec])
    返回time所指时间的Time对象
    Time.at(0,0)
    =>1970-01-01 08:00:00 +0800

    Time.gm(year[, mon[, day[, hour[, min[, sec[, usec]]]]]])
    Time.gm(sec, min, hour, mday, mon, year, wday, yday, isdst, zone)
    Time.utc(year[, mon[, day[, hour[, min[, sec[, usec]]]]]])
    Time.utc(sec, min, hour, mday, mon, year, wday, yday, isdst, zone)
    返回由参数指定的协调世界时的Time对象. 第1参数后面的全是可选参数. 若省略这些参数的话,将使用最小的可能值.
    Time.utc(2013)
    =>2013-01-01 00:00:00 UTC

    Time.local(year[, mon[, day[, hour[, min[, sec[, usec]]]]]])
    Time.local(sec, min, hour, mday, mon, year, wday, yday, isdst, zone)
    Time.mktime(year[, mon[, day[, hour[, min[, sec[, usec]]]]]])
    Time.mktime(sec, min, hour, mday, mon, year, wday, yday, isdst, zone)
    返回由参数指定的地方时的Time对象.
    Time.local(2013,2,14,2,14)
    =>2013-02-14 02:14:00 +0800

    Time.new
    Time.now
    返回当前时间的Time对象. new与now的区别在于,它会调用initialize.
    Time.now
    =>2013-02-14 02:14:00 +0800

    self + other
    返回self之后other秒的时间.
    Time.now+12
    =>2013-02-14 02:14:12 +0800

    self - other
    若other是Time对象的话,就以Float形式返回这二者间的时间差,单位是秒. 若other是数值时, 就返回self之前other秒钟的时间.
    Time.now-12
    =>2013-02-14 02:13:48 +0800

    self <=> other
    时间的比较. other必需是Time对象或数值.若是数值的话, 就把它当做自起算时间以来经过的秒数,然后进行比较.

    asctime
    ctime
    将时间变为asctime(3)形式的字符串. 但不包含末尾的 \n .
    Time.now.ctime
    =>"Mon Mar 18 15:59:04 2013"

    gmt?
    utc?
    若self的时区是协调世界时的话,就返回真.
    Time.now.gmt?
    =>false
    Time.now.utc?
    =>false

    getgm
    getutc
    新生成并返回一个时区设为协调世界时的Time对象.
    Time.now.getgm
    =>2013-03-18 08:01:35 UTC
    Time.now.getgm.gmt?
    =>true

    getlocal
    新生成并返回一个时区设为地方时的Time对象
    Time.now.getlocal
    =>2013-03-18 16:03:16 +0800

    gmtime
    utc
    将时区设为协调世界时.返回self.
    Time.now.utc
    =>2013-03-18 08:01:35 UTC

    localtime
    将时区设为地方时(默认).

    strftime(format)
    按照format字符串的模式,将时间变为字符串,并返回结果. format字符串可以使用下列形式.
    %A: 星期几的名称(Sunday, Monday ... )
    %a: 星期几的简称(Sun, Mon ... )
    %B: 月份的名称(January, February ... )
    %b: 月份的简称(Jan, Feb ... )
    %c: 日期和时间
    %d: 日期(01-31)
    %H: 24时制的小时(00-23)
    %I: 12时制的小时(01-12)
    %j: 一年中的第几天(001-366)
    %M: 分钟(00-59)
    %m: 表示月份的数字(01-12)
    %p: 上午/下午(AM,PM)
    %S: 秒钟(00-60) (60是闰秒)
    %U: 表示周的数字.以第一个星期天作为第一周的开始(00-53)
    %W: 表示周的数字.以第一个星期一作为第一周的开始(00-53)
    %w: 表示星期几的数字. 星期天是0(0-6)
    %X: 时刻
    %x: 日期
    %Y: 表示公历年份的数字
    %y: 公历年份的后两位数字(00-99)
    %Z: 时区trap
    %%: %本身

    sec
    min
    hour
    mday
    day
    mon
    month 
    year
    wday
    yday
    isdst
    dst? 
    zone
    返回时间的要素

    Time.now.ctime
    =>"Mon Mar 18 15:59:04 2013"
    Time.now.wday
    =>1
    Time.now.mday
    =>18
    Time.now.yday
    =>77

    succ
    将self加上1秒钟以后生成并返回一个Time对象. 时区采用地方时.

    utc_offset
    gmt_offset
    gmtoff
    返回一个以秒为单位的数值, 该数值显示了与协调世界时之间的时差.

    Time.now.utc_offset
    =>28800

    to_a
    以一个包括10个元素的数组的形式返回某时间. 数组元素的排列如下.
    sec: 秒 (整数 0-60)
    min: 分 (整数 0-60)
    hour: 时 (整数 1-24)
    mday: 日 (整数)
    mon: 月 (整数 1-12)
    year: 年 (整数 2000钳=2000)
    wday: 星期几 (整数 0-6)
    yday: 一年的第几天 (整数 1-366)
    isdst: 有无夏令时 (true/false)
    zone: 时区 (字符串)

    Time.now
    =>2013-03-18 16:10:03 +0800
    Time.now.to_a
    =>[3,10,16,18,3,2013,1,77,false,"China Standard Time"]

    to_f
    以浮点数形式返回自起算时间以来经过的秒数. 它可以表示那些非整秒的情况

    to_i
    tv_sec
    以整数形式返回自起算时间以来经过的秒数.

    usec
    tv_usec
    返回某时刻的微秒部分.

    to_s
    将某时刻变换为形如date(1)形式的字符串, 等同于:
    elf.strftime("%a %b %d %H:%M:%S %Z %Y")
  • Ruby语言入门(24)- 内部类 - Thread

    2013-03-18 15:17:47

    Thread使得并行编程成为可能。线程是一种共享内存空间并可同时运行的控制流。目前Ruby解释器采用时间片轮转法来控制线程,所以使用线程后并不会提高程序的运行速度。

    启动程序时生成的线程被称作主线程。若主线程因为某些原因而终止运行时,子线程和整个程序也会终止运行。发生异常时,会被送往主线程。

    若启动线程时指定的块结束运行时,该线程也将终结。块的正常结束和因异常等引起的非正常结束都代表快的终结。

    当线程内发生异常,而且没有被rescue捕到时,该线程会被停掉,而且不发出任何警告。如果这时有其它线程因Thread#join而等待这个线程,会在等待的线程中再次引发相同的异常。

    通过下列方法可以保证在线程因发生异常而终止工作时,中断解释器:
    $DEBUG的值设为真(调试模式)。用-d选项来启动ruby解释器也可以取得相同的效果。
    使用Thread.abort_on_exception来设置标识。
    使用Thread#abort_on_exception来设定特定线程的标识。

    线程的状态
    可以使用Object#inspect或Thread#status来查看线程的状态。 状态有:
    run (运行 or 可运行状态)
    sleep (挂起状态)
    aborting (终止处理中)
    dead (终止状态)

    Thread.abort_on_exception
    Thread.abort_on_exception = newstate
    若其值为真的话,一旦某线程因异常而终止时,整个解释器就会被中断

    Thread.critical
    Thread.critical = newstate
    当其值为真时,将不会进行线程切换

    Thread.current
    返回当前运行中的线程

    Thread.exit
    终止当前线程的运行。

    Thread.kill(thread)
    终止指定线程的运行。若该线程已经终止,则不作任何动作。

    Thread.list
    返回处于运行状态或挂起状态的活线程的数组。

    Thread.main
    返回主线程

    Thread.new([arg, ...]) { ... }
    Thread.start([arg, ...]) { ... }
    Thread.fork([arg, ...]) { ... }
    生成线程,并开始对块进行计算.

    Thread.pass
    将运行权交给其他线程. 它不会改变运行中的线程的状态,而是将控制权交给其他可运行的线程

    Thread.stop
    将当前线程挂起,直到其他线程使用run方法再次唤醒该线程.

    self[name]
    取出线程内与name相对应的固有数据.

    self[name] = val
    将线程内与name相对应的固有数据的值设为val

    abort_on_exception
    abort_on_exception = newstate
    它返回布尔值,在赋值形式中,它返回右边的newstate。

    alive?
    若线程是"活"的,就返回true.

    exit
    kill
    terminate 
    终止线程的运行.

    group 
    返回线程所属的ThreadGroup对象.

    join
    join(limit) 
    挂起当前线程,直到self线程终止运行为止. 若self因异常而终止时, 将会当前线程引发同样的异常.


    key?(name)
    若与name相对应的线程固有数据已经被定义的话,就返回true

    keys 
    以数组的形式返回与线程固有数据取得关联的索引.

    priority
    返回线程的优先度. 优先度的默认值为0. 该值越大则优先度越高.

    priority = val
    设定线程的优先度

    raise([error_type,][message][,traceback])
    在该线程内强行引发异常.

    run
    重新启动被挂起(stop)的线程.

    safe_level
    返回self 的安全等级.

    status
    使用字符串"run"、"sleep"或"aborting" 来表示活线程的状态.

    stop?
    若线程处于终止状态(dead)或被挂起(stop)时,返回true.

    value
    一直等到self线程终止运行(等同于join)后,返回该线程的块的返回值.

    wakeup
    把被挂起(stop)的线程的状态改为可执行状态(run).


  • Ruby语言入门(23)- 内部类 -Struct

    2013-03-18 14:44:23

    Struct为结构体类。由Struct.new生成该类的子类。在子类中使用new方法就可以生成构造体。构造体子类中定义了构造体成员的访问方法。

    Struct.new([name,] member ... )
    生成并返回一个名为name的Struct 类的子类。子类中定义了访问结构体成员的方法.结构体名name将成为Struct的类常数名,所以必须以大写字母开始。

    Struct::XXX.new(value,...)
    Struct::XXX[value,...]
    生成并返回结构体对象。参数将成为结构体的初始值。

    Child = Struct.new("Child", :name, :age) //由Struct.new生成该类的子类。
    child1 = Child.new("xixi", 5) //在子类中使用new方法就可以生成构造体。
    child1.age  //构造体子类中定义了构造体成员的访问方法。
    #=> 5
    child2.name
    #=>xixi

    self[nth]
    返回结构体的第nth个成员的值。
    child1[1]
    #=> 5  //child1[1] --> child1.age

    self[nth]=value
    将结构体的第nth个成员的值设定为value,并返回value值。
    child1[1]=11
    child1[1]
    #=> 11

    each {|value| ... }
    依次对结构体的各个成员进行迭代操作。
    child1.each {|v| puts v}
    #=> 
    xixi
    11

    each_pair {|member, value| ... } 
    在结构体中,依次将某成员的名称和值传给块进行迭代操作。
    child1.each_pair{|m,v| p m,v}
    #=> 
    :name
    xixi
    :age
    11

    length/size
    返回结构体的成员数量。
    child1.legth
    #=>2

    members
    以数组形式返回结构体的成员名(字符串)。
    child1.members
    #=>[:name, :age]

    values/to_a
    将结构体的成员的值存入数组,并返回它。
    child1.values
    #=>["xixi", 11]

    values_at(member_1, ... member_n)
    以数组的形式返回参数(数值、字符串或Symbol)所指成员的值。
    child1.values_at(1)
    #=> 11


  • 使用Ruby对XML进行操作

    2013-03-06 15:55:49

    Ruby是用REXML库对XML文件进行解析,路径是: rexml/document 

    所有方法全部包含在模块 module REXML 中,所以在文件头部引用的时候使用如下格式:
    require 'rexml/document'
    include REXML

    首先我们要打开指定的XML文件:
    xmlDoc=File.new('c:\\nodes.xml')
    xmlFile=Document.new(xmlDoc)

    new
    Document类的构造方法,参数可以为一个xml文件的路径,或者一个IO对象。

    puts xmlFile.root

    输出为:
    <root>Root Node<Child1 att1='attvalue1' att2='attvalue2'>Son<Child2>grandson</Child2></Child1></root>

    root
    返回一个element类型的对象,是该xml的根元素。 注意,这时候不是document对象了,是一个element对象。

    puts xmlFile.root.document

    输出为:
    <?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
    <root>Root Node<Child1 att1='attvalue1' att2='attvalue2'>Son<Child2>grandson</Child2></Child1></root>

    document
    返回包含element类型对象的文档, 注意,这时候是document对象了。

    puts xmlFile.root.get_text.value
    puts xmlFile.root.get_text
    puts xmlFile.root.text
    puts xmlFile.root.texts

    输出均为:
    Root Node

    text: 返回第一个text element的string值,为String类
    texts:返回的所有text element的集合,为Array类
    get_text:返回的是一个REXML::Text对象
    value:返回REXML::Text对象的值

    puts  XPath.first(xmlFile, "//Child1" )

    输出为:
    <Child1 att1='attvalue1' att2='attvalue2'>Son<Child2>grandson</Child2></Child1>

    使用XPath找到第一个满足Child1的节点,类型为element

    XPath.each(xmlFile, "//") { |element| puts element }

    返回所有的element

    puts xmlFile.get_elements("//Child2")

    输出为:
    <Child2>grandson</Child2>

    使用xpath查找匹配的element

    xmlFile.root.xpath

    输出为:
    /root

    显示element的xpath,可以看到具体的xpath路径

    puts xmlFile.root.elements[1]

    输出为:
    <Child1 att1='attvalue1' att2='attvalue2'>Son<Child2>grandson</Child2></Child1>

    puts xmlFile.root.elements[1].elements[1]

    输出为:
    <Child2>grandson</Child2>

    以上都是利用index定位element,注意index都是从1开始的,没有0



  • Ruby利用封装模块来创建XML文档

    2013-03-06 10:49:54

    我们来看看怎么通过定制可复用的函数来创建xml文档:

    #coding: utf-8
    require 'win32ole'

    #创建xml文件
    $xmlDoc = WIN32OLE.new('Msxml2.DOMDocument.3.0')

    #创建Root节点
    $Root=$xmlDoc.createElement("RootNode")
    $xmlDoc.appendChild $Root

    #创建新的节点,并作为指定节点child加入
    def newXmlNode(newNodeName, newNodeValue, parentNode)
      $newnode = $xmlDoc.createElement(newNodeName) 
      currentNode=parentNode.appendChild($newnode)
      currentNode.text = newNodeValue
      return $newnode
    end 

    #创建指定节点的属性
    def addNodeAttribute(xmlNodeName,newAttribute, newAttributeValue)
      attrs = $xmlDoc.createAttribute(newAttribute)
      attrs.value=newAttributeValue
      xmlNodeName.setAttributeNode(attrs)
    end

    #创建一个子节点,作为Root的子节点加入
    newnode1=newXmlNode('Child1','Son',$Root)
    #给指定的子节点添加属性1
    addNodeAttribute(newnode1,'att1','attvalue1')
    #创建一个子节点,并作为newnode1的子节点加入
    newnode2=newXmlNode('Child2','grandson',newnode1)
    #给指定的子节点继续添加属性2
    addNodeAttribute(newnode1,'att2','attvalue2')
    #保存xml文件
    $xmlDoc.save('C:/nodes.xml')

    运行结果如下:
  • 使用Ruby生成XML文件

    2013-03-05 18:40:16

    下文为一个ruby生成多层xml文档的实例:

    require 'win32ole'

    #创建xml文件
    xmlDoc=WIN32OLE.new("MSXML2.DOMDocument")

    #设置Root
    Root=xmlDoc.createElement("Root")
    xmlDoc.appendChild Root

    #创建各层子元素

    #层1
    bc1=xmlDoc.createElement("child_1")
      bc1Attribute=xmlDoc.createAttribute("Level-1")
      bc1Attribute.text="1"
      bc1.text="1"
      bc1.setAttributeNode bc1Attribute
    #层2
    bc2=xmlDoc.createElement("child_2")
      bc2Attribute=xmlDoc.createAttribute("Leve-2")
      bc2.setAttributeNode bc2Attribute
      bc2Attribute.text="2"
      bc2.text="2"
    #层3
    bc3=xmlDoc.createElement("child_3")
      bc3.text="3"
    #层4-1
    bc41=xmlDoc.createElement("child_4_1")
      bc41.text="4-1"
    #层4-2
    bc42=xmlDoc.createElement("child_4_2")
      bc42.text="4-2"
      
    #加入各自父节点下
      bc3.appendChild bc41
      bc3.appendChild bc42
      bc2.appendChild bc3
      bc1.appendChild bc2
      Root.appendChild bc1


    #创建 XML processing instruction 并把它加到根元素之前
    header=xmlDoc.createProcessingInstruction("xml","version='1.0'")
    xmlDoc.insertBefore header,xmlDoc.childNodes(0)
    #文件保存
    xmlDoc.Save "c:\\test.xml"

    执行结果如下:
  • 使用excel进行Data Driven的实例

    2013-03-05 17:28:02

    基于ruby的watir web driver

    # encoding: utf-8
    #要求支持中文
    require 'win32ole'
    #支持excel文件操作
    require 'watir-webdriver'
    #watri web driver支持

    #打开数据文件,读入用户名和密码信息
    excel = WIN32OLE::new('excel.Application')
    workbook = excel.Workbooks.Open('d:\rubycode\test.xlsx')
    worksheet = workbook.Worksheets('test')
    worksheet.Activate
    @username = worksheet.Range('a1').Value
    @password = worksheet.Range('b1').Value
    excel.Quit

    #使用Watir 进行自动化测试
    $BASE_URL="http://10.32.148.243:8080"
    $browser=Watir::Browser.new:ie
    $browser.goto($BASE_URL+'/parkinglot/login')
    Watir::Wait.until {$browser.text.include?('停车场管理系统登录')} 

    #从数据文件中得出的测试数据
    $browser.text_field(:name, "username").set(@username)
    $browser.text_field(:name, "password").set('1234')

    $browser.div(:class, "login_btn").click
    Watir::Wait.until {$browser.text.include?('Copyright')} 

    很简单的一个示例
  • Watir Web Driver 对excel 文件的操作

    2013-03-05 15:56:23

    一般我们在Watir中实现data driven时,是依靠ruby的file函数。下面是如何使用excel文件进行data-driven的。

    打开文件需要首先 
    require 'win32ole'

    然后打开文件
    excel = WIN32OLE::new('excel.Application')
    workingbook = excel.Workbooks.Open('d:\rubycode\test.xlsx')

    然后选择需要的sheet

    #依据sheet的名称选择
    workingsheet = workingbook.Worksheet('test') 

    #或者依据顺序号选择
    workingsheet = workingbook.Worksheets(1) 

    #激活可以用select
    workingsheet.Select
    #也可以用Activate
    workingsheet.Actviate

    然后选择你需要的数据

    #第一个cell的值
    workingsheet.Range('a1').Value

    #第一列的1-10的值
    worksheet.Range('a1:a3').Value

    #第一行的a-g的值
    worksheet.Range('a1:b1:c1:d1:e1:f1:g1').Value
    或者
    worksheet.Range('a1:g1').Value

    #任意cell的值
    worksheet.Range('a1:b2:c3:d4:e5:f2:g9').Value

    #第一列第一个到第三列第三个,即a1,a2,a3,b1,b2,b3,c1,c2,c3 
    worksheet.Range('a1:c3').Value

    #判断第一列的非空行数(注意,返回的是第一个空cell)
    line=1
    cells='a1'
    while worksheet.Range(cells).Value
    line=line+1
    cells='a'+line.to_s
    end

    #列出第一列的非空cell
    line=line-1
    cells='a'+line.to_s

    if cells == 'a1'
    rows='a1'
    else
    rows='a1:'+cells
    end
    celldata = worksheet.Range(rows).Value 
    puts cell data

    写入数据

    #单个数据
    worksheet.Range('e2').Value= 'test' 
    #数组值
    worksheet.Range('a5:c5').Value = ['more', 'than', 'one'] 

    然后存储

    workbook.Close(1)
    #对应的是第一个按键
     
    workbook.SaveAs 'mytest.xls'
    # 默认路径是"My Documents"

    关闭文件
    excel.Quit

    注意,操作时不要打开目标文件。

    更多的关于excel的操作,可以参考:
    http://msdn.microsoft.com/zh-cn/library/ff841127(v=office.14).aspx


  • Ruby语言入门(22)- 内部类 -String

    2013-02-06 16:01:37

    String是处理字符串的类。可处理任意长度的字节串。

    在该类的众多方法中,那些方法名尾部是!的方法将会直接修改字符串的内容。此时,使用不带!的同名方法是比较安全的。

    类方法

    String.new(string)
    生成并返回一个与string内容完全相同的新字符串。

    String.new("test")
    =>"test"

    实例方法

    self + other
    将字符串连接起来之后,返回得到的新字符串。

    "test1"+"test2"
    =>"test1test2"

    self * times
    将字符串的内容重复times次之后,返回新字符串

    "test1"*3
    =>"test1test1test1"

    self % args
    字符串的格式化。按照格式化字符串(self)的要求对参数进行格式化之后将其返回。
    若args是数组,则等同于sprintf(self, *args),除此之外,则等同于sprintf(self, args)

    "%#x"%10        
    => "0xa"
    "%#x"%8      
    => "0x8"

    self = = other
    self > other
    self >= other
    self < other
    self <= other
    字符串的比较。

    self << other
    concat(other)
    将other字符串的内容连接到self之后。

    “123”<<"a"
    =>"123a"
    “123”<<1
    =>"123\x01"

    self =~ other
    与正则表达式other进行匹配操作。若匹配成功则返回匹配位置的索引,若失败则返回nil

    "test"=~/./
    =>0
    $~
    =>#<Matchdata "t">

    self[nth]
    以整数形式(字符代码)返回第nth字节的内容(相反地,若想从字符代码变为字符串时,请使用Integer#chr)。若nth为负值,则从字符串尾部算起。若nth超出范围则返回nil。

    "abcd"[0]
    =>"a"
    "abcd"[-2]
    =>"c"

    self[nth, len]
    返回从第nth字节算起的长度为len字节的子字符串。若nth为负数则从字符串尾部算起。若nth超出范围则返回nil。

    "abcd"[-2,2]
    =>"cd"

    self[regexp]
    self[regexp, nth]
    返回最初那个与regexp相匹配的子字符串。与匹配操作相关的信息被存入内部变量$~中。
    若使用了nth参数,则返回最初那个与regexp中第nth个括号相匹配的子字符串。若nth为0,则返回整个匹配的字符串。若匹配失败或没有与nth相对应的括号时,返回nil。

    "test"[/te/]
    =>"te"
    "test"[/te/,0]
    =>"te"

    self[first..last]
    生成并返回一个包含从索引first到索引last之间所有内容的字符串。

    "abcd"[1..2]
    =>"bc"

    self[first...last]
    将字符串的头部看作第0个缝隙,将字符串的末尾看作第self.length个缝隙,然后生成并返回一个包含从第first个缝隙到第last个缝隙之间的所有内容的字符串。

    "abcd"[1...2]
    =>"b"

    self[nth]=val
    以val来替换第nth字节的内容。若val是0到255之间的整数时,就把它看作是字符代码,并以该代码所对应的字符来进行替换操作。

    s1="abcd"
    s1[1]="f"
    s1
    =>"afcd"

    self[nth, len]=val
    以val来替换从第nth字节起长度为len字节的子字符串。若nth为负数则从尾部算起。

    s1="abcd"
    s1[1,2]="f"
    s1
    =>"afd"

    self[substr]=val
    以val来替换字符串中第一个与substr相对应的子字符串。

    s1="abcd"
    s1["bc"]="ff"
    s1
    =>"affd"

    self[regexp]=val
    self[regexp, nth]=val
    以val来替换第一个与正则表达式regexp相匹配的子字符串。若使用了参数nth时,则以val替换第一个与正则表达式regexp中的第nth个括号相匹配的子字符串。若nth为0时,则以val来替换整个匹配部分。

    s1="test"
    s1[/es/]="ff"
    s1
    =>"affd"

    self[first..last]=val
    self[first...last]=val
    以val来替换从first到last之间的内容。

    s1="abcd"
    s1[1..2]="ff"
    s1
    =>"affd"
    s1[1...2]="d"
    s1
    =>"adfd"

    self <=> other
    以ASCII代码的顺序来比较self和other。若self较大时返回正整数,相等时返回0,较小时返回负整数。

    capitalize
    capitalize!
    将首字符(若为字母的话)改为大写字母,其余的改为小写字母。

    "test".capitalize
    =>"Test"
    "TEST".capitalize
    =>"Test"

    casecmp(other)
    该方法与String#<=>一样,是用来比较字符串的顺序的。 它将忽略字母的大小写。

    center(width)
    ljust(width)
    rjust(width)
    center(width[, padding])
    ljust(width[, padding])
    rjust(width[, padding]) 
    分别返回居中、靠左、靠右的字符串。第二参数padding的话,将使用padding来填充空白.

    "test".rjust(10,"*")
    =>"******test"

    chomp([rs])
    chomp!([rs])
    删除字符串尾部的行切分符,该切分符由rs指定。rs的默认值取自变量$/的值。

    "test\n".chomp    
    => "test"

    chop
    chop!
    删除字符串末尾的字符(若字符串末尾是"\r\n"的话,就删除2个字符).

    "test".chop
    =>"tes"

    clone
    dup
    返回一个与原字符串内容相同的新字符串. 对被冻结的字符串使用clone会得到一个同样被冻结的字符串,而使用dup就会得到一个内容相同但未被冻结的字符串.


    count(str[, str2[, ... ]])
    返回在该字符串中str所含字符出现的次数.若给出多个参数,则意味着会使用所有参数的交集.

    "testtesttest".count("s")
    =>3 (count for "s")
    "testtesttest".count("st")
    =>9 (count for "s"+"t")
    "123456789".count('2-8', '3-4')
    =>2

    crypt(salt)
    生成并返回一个由self和salt加密而成的字符串. salt是一个由字母或数字、点(.)和斜线(/)构成的2字节以上的字符串.

    "test".crypt("123")
    =>"126D8rSh5sjUE"

    delete(str[, str2[, ... ]])
    delete!(str[, str2[, ... ]])
    从该字符串中删除str所包含的字符.若给出多个参数,则意味着会使用所有参数的交集.

    "123456789".delete("2-8", "4-6")  
    => "123789"
    "123456789".delete("2378")         
    => "14569"

    downcase
    downcase!
    将字符串中的大写字母都改为小写字母.

    dump
    使用反斜线表示法替换字符串中的非显示字符,并返回修改后的字符串

    "test".dump
    =>"\"test\""

    each([rs]) {|line| ... } (each 报错)
    each_line([rs]) {|line| ... }
    对字符串中的各行进行迭代操作. 此时,rs中的字符串将成为行切分符,行切分符的默认值取自变量$/的值.各line中包含用作切分符的字符串.若将rs设为nil时,则意味着不作行的切分. 若设为空字符串""则将连续的换行当做行切分符(段落模式).

    a="1\n2\n3\n"
    a.each_line {|n| puts n}
    1
    2
    3
    =>"1\n2\n3\n"

    each_byte {|byte| ... }
    对字符串中的各个字节进行迭代操作.

    a="1\n2\n3\n"
    a.each_byte {|n| puts n}
    49
    10
    50
    10
    51
    10
    =>"1\n2\n3\n"

    empty?
    若字符串为空(也就是说其长度为0),则返回真.

    gsub(pattern, replace)
    gsub!(pattern, replace)
    gsub(pattern) {|matched| .... }
    gsub!(pattern) {|matched| .... }
    以replace来替换字符串中所有与pattern相匹配的部分. replace中的\&和\0被替换为匹配的子字符串,而\1 ... \9被替换为第n个括号的内容.在替换字符串replace中,还可以使用\`、\'或\+. 它们分别对应于$`、$'、$+.

    a="test"
    a.gsub!("es","se")
    a
    =>"tset"

    hex
    把字符串看做是16进制数形式,并将其变为整数.

    "123".hex
    =>10

    include?(substr)
    若字符串中包含substr子字符串的话,就返回真.若substr是从0到255之间的Fixnum时,将把它看做是字符代码,若包含该代码所对应的字符,就返回真.

    "test".include?("e")
    =>true

    index(pattern[, pos])
    按照从左到右的顺序搜索子字符串,并返回搜索到的子字符串的左侧位置. 若没有搜索到则返回nil.在参数pattern中,可以使用字符串,0到255之间的字符代码或正则表达式来指定想要搜索的子字符串.若给出了pos时, 则从相应位置开始搜索.省略pos时其默认值为 0.

    "test".index("es")
    =>1
    "test".index("es",3)
    =>nil

    insert(nth, other)
    在第nth个字符的前面插入other字符串.

    "test".insert(2,"ooo")
    =>"teooost"

    intern
    to_sym 
    返回与字符串相对应的符号值(Symbol). 

    "test".intern
    =>:test

    length
    size
    返回字符串的字节数.

    "test".size
    =>4

    match(regexp) 
    match(regexp[, pos]) 
    与regexp.match(self[, pos])相同

    "test".match(/./)
    => #<MatchData "t">

    next
    next!
    succ
    succ!
    返回下一个字符串. 所谓"下一个"是指,按照26个字母顺序或10进制数的顺序继续向下数时得到的结果.

    "abc".next
    =>"abd"
    "123".succ
    =>"124"

    oct
    将字符串看做是8进制字符串,并将其变为整数.

    "123".oct
    =>83

    replace(other)
    以other的内容来替换字符串的内容.

    "test".replace("123")
    =>"123"

    reverse
    reverse!
    对字符串进行反转.

    "123".reverse
    =>"321"


    rindex(pattern[, pos])
    按照从右到左的顺序来搜索子字符串,并返回找到的子字符串的左侧的位置. 若搜索失败则返回nil.在参数pattern中,可以使用字符串,从0到255之间的字符代码或正则表达式来指定想要搜索的子字符串.若给出了pos时,就从相应位置开始搜索. 省略pos时,其值为self.size (右端).

    "character".rindex("c")
    =>5
    "character".rindex("c",0)
    =>0

    scan(re)
    scan(re) {|s| ... }
    使用正则表达式re反复对self进行匹配操作,并以数组的形式返回匹配成功的子字符串.

    "test".scan(/./)
    =>["t","e","s","t"]

    slice(nth[, len])
    slice(substr)
    slice(first..last)
    slice(first...last)
    slice(regexp[, nth])
    与self[ ]相同.

    slice!(nth[, len])
    slice!(substr)
    slice!(first..last)
    slice!(first...last)
    slice!(regexp[, nth])
    从字符串中删除指定的范围(请参考self[ ]),然后返回删除的子字符串.

    a="test"
    a.slice(1)
    =>"e"
    a.slice!(1)
    a
    =>"tst"

    split([sep[, limit]])
    使用sep指定的pattern来分割字符串,并将分割结果存入数组.
    sep可以是下列之一:
    • 正则表达式: 把正则表达式的匹配结果当作切分符来切分字符串。如果使用了括号群组的话,与群组相匹配的字符串也会出现在最后的数组中。
    • 1字节的字符串:把该字符当作切分符来进行切分
    • 2字节以上的字符串:把与Regexp.new(sep)相匹配的字符串当作切分符来进行切分。
    • 省略 或 nil:把$;的值当作切分符来进行切分。
    • 1字节的空白' ' 或 使用了$;且其值为nil时:除了头部的空白之外,使用空白进行切分。
    • 空字符串'' 或 与空字符串相匹配的正则表达式:以单个字符为单位进行切分。可识别多字节字符。

    a="test"
    a.split
    =>["test"]
    a.split("")
    =>["t","e","s","t"]

    squeeze([str[,str2[, ... ]]])
    squeeze!([str[,str2[, ... ]]])
    压缩由str所含字符构成的重复字符串。

    "111222333".squeeze
    =>"123"
    "111222333".squeeze("222")
    =>"1112333"

    strip
    strip!
    删除头部和尾部的所有空白字符。空白字符是指" \t\r\n\f\v"。strip生成并返回修改后的字符串。strip!会修改self本身并返回结果。若没有进行删除动作,则返回nil。

    "   test   ".strip
    =>"test"

    lstrip 
    lstrip! 
    删除字符串头部的所有空白字符。空白字符是指" \t\r\n\f\v"。lstrip会生成并返回加工后的字符串。lstrip!会修改self本身并返回结果。如果没有删除空白字符,则返回nil。

    "   test   ".lstrip
    =>"test   "

    rstrip 
    rstrip!
    删除字符串尾部的所有空白字符。空白字符是指" \t\r\n\f\v"。rstrip会生成并返回加工后的字符串。rstrip!会修改self本身并返回结果。如果没有删除空白字符,则返回nil。

    "  test  ".rstrip
    =>"   test"

    sub(pattern, replace)
    sub!(pattern, replace)
    sub(pattern) {|matched| ... }
    sub!(pattern) {|matched| ... }
    用replace来替换首次匹配pattern的部分。若带块调用的话,就以块的计算值来替换首次匹配的部分。sub生成并返回替换后的字符串。而sub!会修改self本身并返回结果。若没有进行替换时返回nil。
    除去只进行一次匹配这个特点以外,它与gsub是相同的。

    "test".sub("es","ttt")
    ="ttttt"

    sum([bits=16])
    计算字符串的bits位的校验和。

    "test".sum
    =448

    swapcase
    swapcase!
    将所有的大写字母改为小写字母,小写字母改为大写字母。

    "TesT".swapcase
    =>"tESt"

    to_f
    将字符串看作是10进制数形式,并将其变为浮点数Float。

    "123".to_f
    =>123.0

    to_i
    to_i(base) 
    将字符串看作是10进制数形式,并将其变为整数。

    "123".to_i
    =>123

    to_s
    to_str
    返回self。


    tr(search, replace)
    tr!(search, replace)
    若字符串中包含search字符串中的字符时,就将其替换为replace字符串中相应的字符。

    "test".tr("es","aa")
    =>"taat"

    unpack(template)
    按照template字符串的规则,把被pack起来的字符串unpack开来,并以数组的形式返回其结果.

    "test".unpack("a")
    =>["t"]

    upcase
    upcase!
    在ASCII字符串的范围内,将所有字母都变为大写形式。

    upto(max) {|s| ... }
    在从self到max的范围内,依次取出“下一个字符串”后将其传给块,进行迭代操作。

    "a".upto("e") {|s| puts s}
    a
    b
    c
    d
    e
    =>"a"

  • 探究自动测试与手动测试的利与弊(一点个人的看法)

    2013-02-06 15:16:00

    探究自动测试与手动测试的利与弊

    发布时间: 2013-2-06 11:26    作者: 曾月天 译    来源: 51Testing软件测试网采编 
    字体:  小  中  大  | 上一篇 下一篇 | 打印  | 我要投稿  | 推荐标签: 软件测试 手动测试 自动化测试

      几个星期以前我遇到我们团队中的自动测试专家,从他们那里得到了一些什么时候适宜进行自动测试什么时候适合进行手动测试的信息。其实掰掰手指头都能想到,必须使用常识来决定。如果你只需要运行测试一两次,或者做自动测试非常昂贵,那最好采用手动测试。不过,在使用常识之前,你必须拿出确定的一些关于何时开始自动化测试的指导方针。

      自动测试的好处:

      如果你需要反复运行一组测试,那么自动测试将会对你非常有用。(回归测试,build测试,压力测试,负载测试,性能测试,安全测试,缺陷追踪测试等等这些测试场景相同,需要多次执行的,就适合自动化测试)

      自动测试使你能够应对频繁改变的代码从而跟上周期性回归测试的脚步。(不错,所以自动化测试特别适合回归测试)

      自动测试可以使你能够自动运行主流业务场景从而跟上周期性回归测试的脚步。(原文:It gives you the ability to run automation in main stream scenarios to catch regressions in a timely manner)(自动化测试的好处就是在测试框架搭建好后,新的需求变更引起的业务场景的变更,比手工测试的update效率更高,成本更低)

      自动测试可以帮助你测试大量测试矩阵(在不同操作系统上的不同语言)。自动测试可以使你的测试同时运行在不同的机器上,而手动测试必须不断地继续执行。(自动化测试的可以用一套脚本进行版本测试和分布式测试,对于web gui的测试,效果是倍增的。)

      自动测试的限制:

      花费大。编写测试用例,编写和配置自动化测试框架将会在测试开始时花费比手动测试更多的费用。
    (其实自动化测试的成本在任何时刻都是比手工测试高的,并不是自动化测试的速度比手工测试的速度快,就武断的认为自动化测试的成本低。自动化的成本唯一的好处是对一个成型的产品,能够减少回归测试的成本,但是必须是长生命周期的项目,一般2年左右的才有必要去大规模自动化覆盖,对于6个月以内的产品,除非是同架构的产品,否则很难套用原有的测试框架,往往得不偿失)

      无法自动测试一些可视的场景。例如,如果你无法通过代码告诉自动测试工具字体颜色,那么只好使用手动测试。
    (自动化测试的测试用例,首先就要和手工测试用有所区别,两者的scope是有迭代,但是也有各自的独立范畴,如何挑选测测试用例进行自动化转换,是一个技术活,例如,字体的颜色,其实是可以进行自动化验证的,比如固定的模板色的话,或者使用同一CSS文件的情况下)


      手动测试的好处:

      如果一个测试用例在编码阶段只运行两次,那最好使用手动测试,它将比自动测试花费少得多的费用。
    (其实现在很多自动化工具并非是针对case进行转制的,比如cucumber,是基于正则表达式的,往往只需要1周时间,对100多条正则表达式与自动化脚本进行匹配,就能实现全面自动化,所以现在以执行频度去考虑是否进行自动话,实际上已经过时了)

      手动测试允许测试员进行更多的随机测试。以我的经验来看,更多的bug将会由随机测试发现,而不是自动测试。并且,一个测试员花费越多的时间进行随机测试,发现真正的用户bug的几率就越大。

    (随机测试的实现,自动化工具更加有效率,之所以随机测试发现更多的bug,就在于自动化转制时,选择的手工测试用例的覆盖率偏低,overlap的场景太少,业务逻辑和workflow只考虑了正向等等愿意,很多公司在转制自动化时,是使用录制的方式,依据现有的手工测试用例,其实这时,基本上能够录制成功的话,已经很难发现miss的bug,最多只能在后续的版本中发现一些regression bug,所以很多自动化之后,发现没有比手工测试发现更多的bug,就认为自动化测试的效果不佳,是错误的。根源除了以上之外,还是对自动化脚本的target没有过的考虑,从出发点上就是模糊的)

      手动测试的限制:

      手动进行测试将花费大量的时间。
    (手工测试未必就比自动化测试花费的时间更多,有很多操作,自动化因为缺少上下文,所以很多pre-condition需要准备,而手工测试反而可以直接忽略了这些pre-condition)

      每次有了新的build,测试员必须重新运行测试-经过一段时间以后将会非常繁琐和疲惫。
    (其实手工测试的plan和method设置的很好的话,手工测试也很轻松,原来好多公司都经常在一个新版本上进行所有测试用例的回归测试,带来的是麻木和大量的缺失。其实通过引入case的severity和priority,以及bug的分析等因素,完全可以设定选取测试用例的范围,未必要都测试)


      其他的因素:

      你将哪些部分进行自动测试也由你使用的工具决定。如果该工具有很多限制,那么这些部分还是手动测试吧。
    (究竟是根据需求选择测试工具,还是根据测试工具选择测试范围,这是个难题。所以,解决之道还是你想使用自动化测试达到什么效果,你的目标是什么,才能去选择工具和划定范畴,为了纯粹自动化而自动化,是很多公司的现状,完全没有人去考虑我究竟为什么自动化,就为了说我们是自动化测试的?自动化很流行,所以我们也要自动化?自动化的测试工程师才是最好的测试工程师?)

      是否投资的回报值得运行自动测试?是否你自动化测试的产出值得建立和支持测试用例,自动框架和运行测试用例的系统?

    (恩,自动化的过程很复杂,有些公司是为了降低成本,有些公司为了提高竞争力,有些公司是为了锻炼技术,总之,自动化的目的太多了,完全要依据你自己的需求)

      自动测试的标准

      有两个问题可以用来判断是否应该为你的测试用例进行自动化。

      Q1:是否测试场景可以自动化? (
      A1:是的,并且花费很少。
      A2:是的,但是花费很多。
      A3:不,不可能进行自动化。

      Q2:该测试场景有多么重要?
      A1:我必须在任何可能的时候都对其进行测试。
      A2:我需要有规律地对该场景进行测试。
      A3:我只需要测试该场景一次。

      如果这两个问题你的答案都是#1,那么你肯定需要自动化该测试。

    (还是上面说的,自动化的目的是什么才能决定是否自动化,上面两个问题,更像是在自动化过程中,如果进行优先级的安排的问题,那些先自动化,那些后自动化)

      如果这两个问题你的答案是一个#1和一个#2,那么你最好自动化该测试。

      如果这两个问题你的答案都是#2,那么你应该好好考虑一下是否你值得为自动化测试投资。

      如果你无法自动测试,会有什么结果

      让我们假设如果你有一个测试必须在任何可能的时间运行,但是却无法自动化它,你的选择是:

      再评估 – 是否我真的需要如此频繁地运行它?

      如果手动测试它会有多大的花费?

      寻找新的测试工具。

      考虑使用test hooks。

    (没有一种测试工具是万能的,自动化的本质还是个工具,能否成功的进行自动化,还是取决于你测试的基本素质,手工测试能够组织的优秀的团队,进行自动化就是一个游戏的过程)
  • Ruby语言入门(21)- 内部类 -Regexp

    2013-02-06 14:01:19

    Regexp是处理正则表达式的类。正则表达式的字面值是以双斜线内夹表达式的形式生成的。

    /^this is regexp/

    还可以使用Regexp.new(string)来动态地生成正则表达式对象。

    类方法

    Regexp.compile(string[, option[, code]])
    Regexp.new(string[, option[, code]])
    编译string后生成并返回一个正则表达式对象。

    第二参数是可以使用如下的Fixnum值:
    Regexp::IGNORECASE 忽略字符的大小写差异。等同于正则表达式字面值的//i 选项。
    Regexp::MULTILINE 多行模式。正则表达式"."将会匹配换行符。等同于正则表达式字面值的//m 选项。
    Regexp::EXTENDED 忽略从(未以反斜线进行转义的)空白以及#到换行之间的内容。等同于正则表达式字面值中的//x 选项。

    若第二参数并非Fixnum,则被看作是布尔值,若为真(nil,false以外的值)的话,其效果等同于指定了Regexp::IGNORECASE。

    若指定了第三参数的话,进行匹配时将使用指定的字符编码而不受$KCODE的影响。


    Regexp.new("a")
    =>/a/
    Regexp.new("\a")
    =>/\x07/

    Regexp.escape(string[,kcode])
    Regexp.quote(string[,kcode])
    在string中的“特殊字符”前面插入转义字符(反斜线)后返回该字符串,以可选参数kcode来设定字符串的字符编码。

    Regexp.quote("a")
    =>a
    Regexp.quote("\a")
    =>"\a"

    Regexp.last_match
    返回当前范围内的最后一次正则表达式匹配的MatchData对象。调用该方法与调用$~是一样的。

    Regexp.last_match([nth]) 
    若整数nth为0,则返回匹配的字符串($&)。除此以外,则返回与第nth个括号相匹配的部分字符串($1,$2,...)。若没有相应的括号或未完成匹配,则返回nil。

    /(.)(.)/ =~ "ab"
    Regexp.last_match       
    => #<MatchData: "ab" 1:"a" 2:"b">
    Regexp.last_match[0]   
    => "ab"
    Regexp.last_match[1]   
    => "a"
    Regexp.last_match[2]   
    => "b"
    Regexp.last_match[3]   
    => nil

    Regexp.union([pattern, ...])
    用|将传给参数的pattern连起来之后,以Regexp的形式将其返回。只要与其中的一个pattern相匹配,就意味着与Regexp相匹配。

    Regexp.union("a", "b", "c")
    => /a|b|c/
    Regexp.union("a", "b", "c")=~"c"
    =>0
    $~
    =>#<MatchData: "c">
    $~[0]
    =>"c"

    实例方法

    self =~ string
    self = = = string
    与string字符串进行正则表达式的匹配操作。若匹配成功则返回匹配位置的索引(首位为0)。若匹配失败或者string为nil时,返回nil。内部变量$~中保存的是与匹配相关的信息。


    ~ self
    与$_变量的值之间进行匹配操作。等同于self =~ $_

    casefold?
    若编译正则表达式时不区分大小写,则返回真。

    kcode
    采用与$KCODE相同的形式返回编译正则表达式时的字符编码。若编译正则表达式时没有固定的编码(使用匹配时的$KCODE的值)时,返回nil。

    match(str)
    match(str, [pos])
    除去返回MatchData对象这点区别以外,它与self =~ str是相同的。匹配失败时返回nil。

    Regexp.union("a", "b", "c").match("a")
    =>#<MatchData: "a">

    若使用了第二可选参数 pos 的话,将从 pos 所指位置开始进行匹配(pos的默认值为0)。

    /(.).(.)/.match("test", 2)  
    =>#<MatchData: "st" 1:"s" 2:"t"> 

    options
    返回生成正则表达式时的选项。返回值是Regexp::EXTENDED、Regexp::IGNORECASE和Regexp::MULTILINE间的任意组合。

    Regexp.union("a", "b", "c").options
    =>-1

    source
    生成并返回正则表达式的原本的字符串形式。

    Regexp.union("a", "b", "c").source
    =>"a|b|c"

    to_s 
    生成并返回正则表达式的字符串形式

    Regexp.union("a", "b", "c").to_s
    =>"<?-mix:a|b|c>"



    正则表达式


    \  
    将下一个字符标记为一个特殊字符. 例如,“\n”匹配一个换行符。串行“\\”匹配“\”。

    ^
    匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。

    $
    匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。

    *
    匹配前面的子表达式零次或多次。例如,zo*能匹配“z”以及“zoo”。

    +
    匹配前面的子表达式一次或多次。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。

    ?
    匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“does”或“does”中的“do”。

    {n}
    n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。

    {n,}
    n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。

    {n,m}
    m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o。

    ?
    当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o+?”将匹配单个“o”,而“o+”将匹配所有“o”。

    .
    匹配除“\n”之外的任何单个字符。要匹配包括“\n”在内的任何字符,请使用像“(.|\n)”的模式。

    (pattern)
    匹配pattern并获取这一匹配。要匹配圆括号字符,请使用“\(”或“\)”。

    (?:pattern)
    匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。
    例如“industr(?:y|ies)”就是一个比“industry|industries”更简略的表达式.

    (?=pattern)
    正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。
    例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。

    (?!pattern)
    正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。
    例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。

    (?<=pattern)
    反向肯定预查,与正向肯定预查类拟,只是方向相反。
    例如,“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”。

    (?<!pattern)
    反向否定预查,与正向否定预查类拟,只是方向相反。
    例如“(?<!95|98|NT|2000)Windows”能匹配“3.1Windows”中的“Windows”,但不能匹配“2000Windows”中的“Windows”。

    x|y
    匹配x或y。例如,“z|food”能匹配“z”或“food”。“(z|f)ood”则匹配“zood”或“food”。

    [xyz]
    字符集合。匹配所包含的任意一个字符。例如,“[abc]”可以匹配“plain”中的“a”。

    [^xyz]
    负值字符集合。匹配未包含的任意字符。例如,“[^abc]”可以匹配“plain”中的“p”。

    [a-z]
    字符范围。匹配指定范围内的任意字符。例如,“[a-z]”可以匹配“a”到“z”范围内的任意小写字母字符。

    [^a-z]
    负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]”可以匹配任何不在“a”到“z”范围内的任意字符。

    \b
    匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b”可以匹配“never”中的“er”,但不能匹配“verb”中的“er”。

    \B
    匹配非单词边界。“er\B”能匹配“verb”中的“er”,但不能匹配“never”中的“er”。

    \cx
    匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c”字符。

    \d
    匹配一个数字字符。等价于[0-9]。

    \D
    匹配一个非数字字符。等价于[^0-9]。

    \f
    匹配一个换页符。等价于\x0c和\cL。

    \n
    匹配一个换行符。等价于\x0a和\cJ。

    \r
    匹配一个回车符。等价于\x0d和\cM。

    \s
    匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。

    \S
    匹配任何非空白字符。等价于[^ \f\n\r\t\v]。

    \t
    匹配一个制表符。等价于\x09和\cI。

    \v
    匹配一个垂直制表符。等价于\x0b和\cK。

    \w
    匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。

    \W
    匹配任何非单词字符。等价于“[^A-Za-z0-9_]”。

    \xn
    匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。
    例如,“\x41”匹配“A”。“\x041”则等价于“\x04&1”。正则表达式中可以使用ASCII编码。.

    \num
    匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1”匹配两个连续的相同字符。

    \n
    标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值。

    \nm
    标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\nm将匹配八进制转义值nm。

    \nml
    如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。

    \un
    匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。


    常用正则表达式

    用户名
    /^[a-z0-9_-]{3,16}$/

    密码
    /^[a-z0-9_-]{6,18}$/

    十六进制值
    /^#?([a-f0-9]{6}|[a-f0-9]{3})$/

    电子邮箱
    /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/
    /^[a-z\d]+(\.[a-z\d]+)*@([\da-z](-[\da-z])?)+(\.{1,2}[a-z]+)+$/

    URL
    /^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/

    IP 地址
    /((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)/

    /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/

    HTML 标签
    /^<([a-z]+)([^<]+)*(?:>(.*)<\/\1>|\s+\/>)$/

    删除代码\\注释
    (?<!http:|\S)//.*$

    Unicode编码中的汉字范围
    /^[\u2E80-\u9FFF]+$/







  • Ruby语言入门(20)- 内部类 -Range

    2013-02-06 13:36:04

    Range类是范围对象的类。范围对象是由范围操作符..或...生成的。由..操作符生成的范围对象包括范围终点,而由...操作符生成的范围对象不包括范围终点。

    for i in 1..5 (range)
       ...
    end


    Range.new(first,last[, exclude_end])
    生成并返回一个从first到last的范围对象。若exclude_end为真,则该对象不包含范围终点。若省略exclude_end,则包含范围终点。生成对象时,会执行 first <=> last 以检查参数的合法性。

    Range.new(1,5)
    =>1..5
    Range.new(1,5,true)
    =>1...5
    Range.new(1,5,false)
    =>1..5

    self === other
    include?(other) 
    若other在范围内则返回真。===主要用在case句的比较中.

    r1=Range.new(1,5)
    r1 === 2
    =>true
    r1.include?(5.1)
    =>false

    比较字符串时,include?将按照字典顺序进行比较
    ("a" .. "c").include?("ba") 
    => true
    ("a" .. "c").member?("ba")
    => false

    begin
    first
    返回最初的元素。

    r1.begin
    =>1

    each {|item| ... }
    对范围内的元素进行迭代操作。

    r1.each{|n| puts n}
    1
    2
    3
    4
    5
    =>1..5

    end
    last
    返回范围终点。这与范围对象是否包含范围终点无关。

    (1..5).end   
    => 5
    (1...5).last  
    => 5

    exclude_end?
    范围对象不包含范围终点时返回真。


    (1..5).exclude_end? 
    => false
    (1...5).exclude_end?
    => true

    step([s]) {|item| ... }
    以s的步长对范围内的元素进行迭代操作。s是正整数。默认值为1。

    (1..5).step(2) {|n| puts n}
    1
    3
    5
    =>1..5

    (1...5).step(2) {|n| puts n}
    1
    3
    =>1...5


  • Ruby语言入门(19)- 内部类 -Numeric

    2013-02-05 16:07:21

    Numeric是数值的抽象类.包含Integer和Float类。
    Integers是整数的抽象类. 其子类包括Fixnum 和 Bignum.
    Float类是浮点数的类.

    方法有:

    self + other
    self - other
    self * other
    self / other
    self % other 余数(模)
    self ** other
    算术操作符

    self <=> other 
    self = = other
    self < other
    self <= other
    self > other
    self >= other
    比较操作符.

    ~ self
    self | other
    self & other
    self ^ other 异或
    位操作符. 

    self << bits
    self >> bits
    移位操作符. 向右(左)移动bits位.

    []
    返回数组

    [1.0]
    =>[1.0]

    + self 
    返回self

    +1
    =>1
    +-1
    =>-1

    - self 
    反转self的符号后将其返回.

    -1
    =>-1
    --1
    =>1

    abs 
    返回self的绝对值.

    -1.abs
    =>1

    ceil 
    返回一个等于或大于self的最小的整数.

    1.3.ceil
    =>2

    chr
    返回数字对应的字符.

    68.chr
    =>"D"

    coerce(number)
    将number变为可直接与自己进行计算的类型,然后将其存入形如[number, self]的数组并返回它.

    a=1
    a.coerce(2.0)
    =>[2.0, 1.0]

    div(other)
    返回self除以other的整数商.

    3.div(2) 
    => 1

    divmod(other)
    以数组[q, m]的形式返回self除以other所得的商(q)和余数(m).

    7.divmod(2) 
    => [3,1]

    downto(min) {|n| ... }
    循环起点为self,步长为-1,循环终点为min

    5.downto(1) {|n| puts n}
    5
    4
    3
    2
    1
    =>5

    eql?
    是否相等

    1.eql?(2)
    =>false

    floor
    返回一个不超过self的最大的整数.

    1.2.floor
    =>1
    1.2.ceil
    =>2

    finite?
    若某数值既非∞又非NaN则返回真

    1.0.finite?
    =>true

    hash
    返回数值的hash

    1.hash
    =>167445871

    infinite?
    若某数值为+∞则返回1, 若为-∞则返回-1, 除此以外返回nil.浮点数除以0得∞.

    1.0.infinite?
    =>nil

    integer?
    若self为整数则返回真.

    -1.ineger?
    =>true

    id2name (no method)
    返回与Symbol对象的整数值(可用Symbol#to_i获得该整数值)相对应的字符串. 若没有与整数相对应的符号的话就返回nil.

    modulo(other)
    返回self除以other后的余数m

    3.divmod(4)
    =>[0,3]
    3.modulo(4)
    =>3

    nan?
    当某数值为NaN(Not a number)时返回真. 浮点数的0除以0得NaN.

    1.0.nan?
    =>false

    next
    返回下一个紧跟的整数,等同于succ

    3.next
    =>4

    nonzero?
    0则返回nil,非0则返回self.

    1.nozero?
    =>1

    quo(other)
    除法运算,它会尽量返回一个接近实数商(quotient)的值.

    1.quo(3)
    =>(1/3)

    remainder(other)
    返回self除以other后的余数r

    3.divmod(1)
    =>[3,0]
    3.remainder(1)
    =>0

    round
    返回最接近self的整数.

    1.4.round
    =>1
    1.5.round
    =>2

    size
    返回所占的bit数

    1.size
    =>4
    22342342342342324.size
    =>8

    succ
    返回下一个紧跟的整数

    3.succ
    =>4

    times {|n| ... }
    循环self(从0到self-1)次

    5.times {|n| puts n}
    0
    1
    2
    3
    4
    =>5

    to_i
    to_int
    返回self的整数部分.

    1.3.to_int
    =>1

    to_f
    将数值变为浮点数(Float).

    1.to_f
    =>1.0

    to_s
    to_s(base)
    将整数变为10进制字符串形式.如果带参数,以base为进制,从2-36,转换为字符

    10.to_s
    =>"10"
    10.to_s(2)
    =>"1010"

    to_sym (no method)
    返回与对象的整数值self相对应的Symbol对象

    truncate
    舍弃小数点后面的部分.

    1.3.truncate
    =>1

    zero?
    若为0则返回真.

    1.3.zero?
    =>false

    step(limit) {|n| ... } 
    step(limit, step) {|n| ... } 
    循环执行块的内容,循环起点是self,步长为step,终点是limit. 同时,step也可以是负数(缺省设置为1).另外,limit和step还可以是Float等.

    1.1.step(1.5, 0.1) {|n| puts n}
    1.1
    1.2
    1.3
    1.4
    1.5

    upto(max) {|n| ... }
    循环起点为self,步长为1,循环终点为max.

    0.upto(5) {|n| puts n}
    0
    1
    2
    3
    4
    5
    =>0

    大多数与数值有关的方法都会在子类中得到重新定义. 这一方面是为了提高效率,另一方面是因为在上级抽象类中无法定义具体细节所致. 请参考下表来了解一下到底各个类中有些什么方法.

  • Ruby语言入门(18)- 内部类 -MatchData

    2013-02-05 15:22:54

    MatchData为处理与正则表达式的匹配过程相关的信息而设置的类. 可以通过下列途径得到该类的实例:
    Regexp.last_match
    Regexp#match, String#match
    $~

    实例方法

    self[n]
    返回第n个子字符串. 0表示整个匹配部分. 若n为负值,则从尾部算起(末尾的元素是第-1个). 若不存在第n个元素则返回nil.

    /(this)(is)(a)(TEST)?/ =~ "thisisatest"
    $~.to_a       
    => ["thisisa", "this", "is", "a", nil]
    $~[0]         
    => "thisisa"
    $~[4]         
    => nil        (不匹配)
    $~[5]         
    => nil        (超出范围)
    $~[-2]        
    => "a"

    self[start..end]
    返回从start位置开始到end位置结束的字符串集合.

    $~[1..4]
    =>["this", "is","a", nil]

    self[start, length]
    返回从start位置开始长度为length的字符串集合.
    $~[1,4]
    =>["this", "is","a", nil]

    begin(n)
    返回第n个子字符串的首字符的偏移量(offset). 0表示整个匹配部分. 若n超出范围则引发IndexError异常. 若第n个子字符串没有匹配部分则返回nil.

    /(this)(is)(a)(TEST)?/ =~ "thisisatest"
    $~.to_a       
    => ["thisisa", "this", "is", "a", nil]
    $~.begin(0)
    =>0 
    $~.begin(1)
    =>0
    $~.begin(2)
    =>4 
    $~.begin(3)
    =>6 
    $~.begin(4)
    =>nil 

    end(n)
    返回第n个子字符串的尾字符的偏移量(offset). 0表示整个匹配部分. 若n超出范围则引发IndexError异常. 若第n个子字符串没有匹配部分则返回nil.

    /(this)(is)(a)(TEST)?/ =~ "thisisatest"
    $~.to_a       
    => ["thisisa", "this", "is", "a", nil]
    $~.end(0)
    =>7 
    $~.end(1)
    =>4 
    $~.end(2)
    =>6 
    $~.end(3)
    =>7 
    $~.end(4)
    =>nil

    captures 
    返回一个包含$1,$2,...的数组. 与to_a不同的是,它不含$&. 若群组没有匹配部分,则相应的数组元素为nil.

    /(this)(is)(a)(TEST)?/ =~ "thisisatest"
    $~.to_a       
    => ["thisisa", "this", "is", "a", nil]
    $&
    =>"thisisa"
    $~.captures
    =>["this", "is", "a", nil]

    length
    size
    返回子字符串的数量(与self.to_a.size相同).

    $~.size
    =>5

    offset(n)
    返回数组[start, end],它包含第n个子字符串的偏移量.

    $~.offset(1)
    =>[0,4]  ("this")

    post_match
    返回匹配部分之后的字符串(与$'相同).

    $~.post_match
    =>"test"

    pre_match
    返回匹配部分之前的字符串(与$`相同).

    /(is)(test)?/ =~ "thisistest"
    $~.pre_match
    =>"this"

    select { ... }
    与self.to_a.select { ... } 相同.

    string
    返回匹配对象的拷贝.返回的字符串已被冻结.

    $~.string
    =>"thisisatest"

    to_a
    返回包含$&,$1,$2,... 的数组。

    /(this)(is)(a)(TEST)?/ =~ "thisisatest"
    $~.to_a       
    => ["thisisa", "this", "is", "a", nil]

    to_s
    返回整个匹配部分.

    /(this)(is)(a)(TEST)?/ =~ "thisisatest"
    $~.to_s 
    =>"thisisa"


    values_at(index1, index2, ...)
    返回一个数组,它包括与正则表达式中第indexN个括号相匹配的子字符串. 与$&一样,第0个表示整个匹配部分.

    /(this)(is)(a)(TEST)?/ =~ "thisisatest"
    $~.values_at(1,2)
    =>["this", "is"]



1824/10<12345678910>
Open Toolbar