rails rspec测试

来源:互联网 发布:数控螺纹g76编程格式 编辑:程序博客网 时间:2024/05/19 03:45
基本介绍
      RSpec由Steven Baker开发并在2005年发布,全面支持Ruby程序的BDD开发测试方式,并且对于Rails程序有着良好的支持,针对View,Controller和Model每一层都有良好的支持。RSpec目前的最新版本是:2.4.0。
详细介绍
项目周期
      在基于BDD开发模式的项目中,以Rails项目开发为例,有如下步骤:
1、 对一个项目立项后,首先进行故事分解。要对系统故事分解,要求我们做到基本的需求分析和对系统的概要设计。
2、 专注于一个用户故事,对其进行详细定义,这一阶段也就是我们编写RSpec测试用例的阶段。通过分析用户故事和用户场景的方式对系统的行为进行详细的定义,把定义写在我们的测试用例中。在这一阶段,包括了我们的传统的软件工程周期中的需求分析和详细设计阶段,需求分析体现在分析系统的行为上,详细设计体现在用例编写时需要为系统实现定义大量的接口,这一点下边细讲。
3、 对于该用户故事,实现View层:
      3.1、运行RSpec测试用例,结果失败;
      3.2、编码实现该view;
      3.3、重新RSpec运行测试用例,通过;
      3.4、重构代码;
4、 对于该用户故事,实现controller层,重复3.1-3.4的过程。
5、 对于该用户故事,实现model层,重复3.1-3.4的过程。
6、 重构三个层次的代码,完成该用户故事。
7、 重复3-6的过程,完成系统其他用户故事。
8、 系统集成,集成测试,确认测试,beta测试等。
9、 系统交付及后期维护。
      这个过程图示如下:
     
      这里强调了按照view、controller到model的顺序来做开发,这是BDD鼓励从外向里的方式来开发程序,先实现与用户直接交互的view层,然后是核心业务层controller,最后是与数据库交互的model层,BDD不但是我们开发程序中运用的一种技术,同时也可以尽早的将系统的用户接口体现出来,体现系统的商业价值,对于开发,用户与系统交互时的行为是系统的核心业务,先将用户接口开发出来,有助于我们专注于实现核心业务,而不浪费精力于其他方面。
安装配置
      RSpec的安装很简单,利用RubyGems在命令行运行gem install rspec命令就可以为Ruby安装rspec的程序包,若是Rails项目,还需要安装rails-rspec插件(运行gem install rails-rspec),可以通过-v选项指定具体要安装的版本。需要注意的是Ruby、Ruby on Rails框架、RSpec框架和rails-rspec插件更新速度较快,很容易出现不兼容的问题,总的来说,Ruby版本要1.8.7及其以上才支持Rails3,, Rails3以前的版本不支持RSpec2,RSpec的版本和rails-rspec插件的版本最好一样,还需注意的是最新版本的Ruby对中文支持较差,必须在Ruby文件的第一行指定编码方式才可以正常运行。
      普通的Ruby程序可以直接编写测试用例并使用”rspec */*.rb”命令(此为RSpec2的用法,RSpec1用”spec */*.rb”命令运行)来运行spec文件。在一个Rails项目中如何使用RSpec呢?下边介绍:
1、在GemFile里面添加如下代码:
      
      此步作用是为Rails程序的测试环境和开发环境添加RSpec支持,指定Rails程序需要加载的Rspec程序库和rspec-rails插件的库以及他们的版本。
2、在Rails程序根目录下使用bundler运行”bundle install”命令为Rails程序安装依赖的程序包。
3、在Rails程序根目录下运行”rails generate rspec:install”命令,此命令的作用是为Rails程序安装RSpec框架,使RSpec取代Test::Unit而存在,这条命令会建立’.rspec’文件、’spec’目录和’spec/spec_helper.rb’文件,他们分别的作用是:
   ‘.rspec’文件:存放运行rake spec任务时,RSpec需要加载的配置信息。
   ‘spec’目录:我们的RSpec测试用例的存放空间,针对mvc,我们可以建立models、views和controllers目录分别存放模型、视图和控制器各个层面的测试用例,每一个测试文件以_spec结尾。
   ‘spec/spec_helper.rb’文件:运行测试用例的RSpec配置文件。每一个以_spec结尾的文件都需要引入该文件,即在文件开头添加:require ‘spec_helper’代码。
      安装搞定。
RSpec如何组织测试用例
      先看下边的代码:
      
      上边是一个比较完整的体现RSpec组织测试用例的模板,当然这里只是为了说明一个_spec文件如何组织测试用例及基本用法,所以我没有去编写每一个测试用例的体,并且例子出现的字符串内容不针对于具体的系统,没有实际意义。下边依次解释含义:
      require ‘spec_helper’:目的是加载’spec/spec_helper.rb’文件中的RSpec配置,运行时需要。
describe()方法:
      我们用describe()方法定义一个测试用例组,describe()方法的参数可以是我们要测试的对象(如例中的People),可以是一个字符串,用来描述该组,describe方法后的字符串所描述的内容可以对应一个用户故事。
      注意describe()方法是可以嵌套的,两个describe()方法衔接起来可以更细化一个用户故事,如上边的里面的describe()方法内就表示:”People have enough money pay for house”。
it()方法:
      我们用it()方法定义一个具体的测试用例(在RSpec中,称一个测试用例为一个example)。其后的字符串为该方法的参数,用来描述一个具体的场景,it方法体就是我们对系统在该场景下的行为的定义。
      It()方法和describe()方法衔接起来,可以完整的描述一个系统行为,以上边的最后的一个测试用例为:”People have enough money pay for house should travel ”。
context()方法:
      Context()方法和describe()方法的作用一样,不同点在于describe倾向于描述我们的测试对象,而context()方法倾向于用字符串描述用户故事。
before()和after():
      这两个方法很多测试框架都支持,需要说明的是这两个方法的参数,例子中为符号’:each’,表明对于每一个测试用例,先执行before()方法中的代码,用例完成后执行after()方法中的代码,若参数为’:all’,则表示在所有的测试用例执行之前,先执行before()方法中的代码,所有的用例完成后执行after()方法中的代码。
      RSpec还提供around()方法,暂不懂其用法,之后的报告中补上。
帮助方法:
      所谓的帮助方法就是把多个测试用例中重复的操作抽出作为一个公用的方法,提高代码重用度。如例子中的work_hard()方法。
共享行为:
      系统的某一个行为是很多场景下都具有的,那么我们可以把它定义为一个共享行为,我们通过share_examples_for()方法定义共享行为,使用it_behaves_like()方法共享定义过的系统行为,如例子中的share_examples_for “any people”, it_behaves_like “any people”。
pending()方法:
      我们确定系统拥有一个行为,但是还没有具体定义,这时我们可以将该行为使用pending()方法来设置该行为为待定义,其后的字符串参数将在生成的报告中显示。
      pending()方法的另外一种用法就是,当一个测试用例失败时,我们可以利用pending方法设置其为一个待修复的bug,方法体内包含使用例失败的代码。例如最后一个测试用例的内容。
RSpec如何编写测试用例
      一个测试用例的永远都有三个部分:准备请求参数,设置期望输出,实际输出和期望输出做校验。
      RSpec的测试用例也不另外,不过也存在着很大的区别。区别在于:
      首先于形:代码接近于描述,一行代码就是一个句子一样容易读懂。
      其次于神:用例在描述系统的一个行为,他要求我们对系统做了什么给出定义。
      这两点是RSpec的核心所在,也是BDD的核心所在。
      对于第一点,得益于Ruby语言本身具有的DSL特性,加之RSpec对其的扩展,使得其实现并不困难。
      对于第二点,是最核心的价值所在,这需要我们倾心设计。
      以前我对于测试用例的看法是,希望一个测试用例尽可能多的覆盖代码,这样容易从集成的角度来寻找bug,BDD认为一个牵扯过多的测试用例不是一个好的测试用例,它提倡我们尽可能的关注于一点,对于其他的需要执行的代码通过模拟实现,这一点怎么做到?答案是:模。
      模是在一个测试用例中实际对象的替代品,我们经常称之为模对象。我们可以设置模对象的各种属性行为,让它模拟实际对象的操作,这样我们的测试用例就可以专注于要测的行为这一点上,看具体例子:
      
      上边的这个测试用例描述的行为是:系统修改bug状态成功后应该跳到bug首页并且提示“更新成功“。
      不要被这样的代码吓到,稍作解释:
      第一行建立一个IssueStatus的模对象;
      第二行的意思是IssueStatus类应该接受find请求(调用IssueStatus的find()方法),请求参数是::id=>11,并且给返回一个名为issue_status的对象;
      第三行的意思是issue_status对象接受update_attributes请求,请求参数为::issue_status=>”fixed”时,返回true;
      第四行,使用get方法对系统的update发请求,参数为::id=>11;
      第五行意思是返回应该重定向到index action上面;
      第六行的意思是提示信息应该是“更新成功“。
      看了下边的提示以后再回头看上面的代码,是不是觉得一行代码读下来有读一个句子的感觉?然后可以按照这种理解的思路读其他的测试用例中定义的系统的行为。
      在这里对测试用例描述系统行为这一点的理解上还是很混是不是?再解:外围的describe()方法和it()方法后边的字符串参数可以帮助我们定位我们要描述的系统的行为,在测试用例体内描述具体的系统行为,回到上边的例子中,我们要描述“修改bug状态成功“这一行为,这个行为时:系统应该根据传入的id(id是要修改状态的bug的id)调用IssueStatus类的find()方法得到一个名为issue_status的对象,通过update_attributes()实例方法来修改issue_status对象的状态,系统最后应该重定向到index action上面,并且提示“更新成功”的信息。
      再对这个测试用例进行思考,可以发现:验证的是结果,测试的是过程,描述的是行为,我们验证系统有没有重定向、有没有提示信息这样的结果,但是我们也给出了系统应该对相关对象做什么请求的过程,这些综合起来构成了系统的一个行为。这就要求我们对系统接口给出详细的定义,甚至可以反映系统各模块之间如何做交互,为开发人员节省不少时间。
      还可以发现一点,因为有模代替,系统根本没有真的去数据库修改bug状态,只不过是走了一遍这样的流程,行为的概念在这里无比的清晰。
      下边代码是该update()方法的实现:
      
      其中else中的路径应该另外编写测试用例来覆盖。
模的使用
      上边的例子中采用模代替了部分实现,现在具体介绍一下模的使用方法:
      新建一个模对象:
     
      mock_model()方法专门用于建立model层对象,其余的三个之间的区别暂时不懂。
      指定模对象的行为:
     
      第一行stub()方法指定People类接受name()方法调用并且传入参数为:id=>0001时,返回“Wu Huanzheng”。with()方法表示传入的参数,and_return()方法表示返回值。
      第二行should_receive方法具有强制性,表示系统必须要调用指定的方法,其余和stub()方法一样。
      第三行at_most(3).times表示至多三次。
      第四行should_not_receive表示明确系统不该调用此方法。
      模对象的行为很强大,可以模拟一切真实调用,还有很多用法,可以通过查看api获得。
校验返回
      RSpec针对所有的返回都有自己的校验方法,看下边例子:
     
      第一行result应该等于5;
      第二行result应该包含5;
      第三行result应该响应hello;
      第四行lambda匿名函数应该抛出错误信息“Nothing find!”;
      第五行result应该匹配正则表达式;
      第六行期望代码块能把bug状态从“open”改为“fixed”;
      第七行result应该为空;
      Should()方法表示肯定,还有should_not()方法表示否定,该方法的工作机制是先执行should或者should_not后边的代码,返回一个match对象,进而把此match对象作为参数传给should()或者should_not()方法,和拥有对象句柄的本地match对象作对比校验,若匹配不成功,返回相应信息。了解更多使用,也可以通过查阅api获取。
Rails测试
      了解了以上内容,其实Rails每一层的测试也非常明了了,具体总结如下:
      View层:读取每一个控件并渲染,验证渲染后的结果为期望值;
      Controller层:使用get或者post方法调用一个action,验证其满足用例中描述的行为;
      Model层:这一层不使用模,需要读取数据库里的数据做验证。
      RSpec的介绍到此告一段落,理解错误之处欢迎指正。