rspec 入门教程

字数 1991阅读 19771

这是一个简单的关于Rails Rspec的简单的介绍

1 安装Rspec

在Rails的配置文件Gemfile配置文件中,配置下面信息

 group :development, :test do
  gem 'rspec-rails', '2.13.1'
 end

我们没有必要单独的安装RSpec ,因为它是rspec-rails的依赖件
会被自动安装.
执行bundle install 或者 bundle install --without production 来安装使用的gem.
rails generate rspec:install
设置让Rails使用RSpec 而不用Test::Unit. 该命令执行完毕之后,会产生一个文件夹spec,该文件加下面有spec/spec_helper.rb 这个文件,spec_helper.rb用来设置测试的配置信息.
下面是spec的固定的规范,固定的格式.

describe XXX do
  it XXX do
       ......
  end
end

包含了一个 describe 块以及其中的一个测试用例(sample),以 it "..." do 开头的代码块就是一个用例.

2 pending 测试大纲
  describe xxxController do

  describe 'GET #show' do
    it "assigns the requested xxx to @xxx"
    it "renders the :show template"
  end

  describe 'GET #new' do
    it "assigns a new xxx to @xxx"
    it "renders the :new template"
  end

  describe 'GET #edit' do
    it "assigns the requested xxx to @xxx"
    it "renders the :edit template"
  end

  describe "POST #create" do
    context "with valid attributes" do
      it "saves the new xxx in the database"
      it "redirects to xxx#show"
    end

    context "with invalid attributes" do
      it "does not save the new xxx in the database"
      it "re-renders the :new template"
    end
  end

  describe 'PUT #update' do
    context "with valid attributes" do
      it "updates the xxx in the database"
      it "redirects to the xxx"
    end

    context "with invalid attributes" do
      it "does not update the xxx"
      it "re-renders the #edit template"
    end
  end

  describe 'DELETE #destroy' do
    it "deletes the xxx from the database"
    it "redirects to users#index"
  end

  describe 'GET #index' do
      it "response.status eq 200"
      it "renders the :index view"
  end
end
3 简单的测试

测试代码的结构

describe AA do
  it 'should do something' do
   something.should
  end
end

使用 http://railstutorial-china.org/ 里面的列子,来做一个简单的测试演示.
rails generate controller StaticPages home help --no-test-framework
该命令会在app/controller文件下生成static_pages_controller.rb,并且会在路由里面生成help,home对应的路由.

match '/help',    to: 'static_pages#help',    via: 'get'
match '/home',    to: 'static_pages#home',    via: 'get'

之后运行下面命令,产生spec/requests/static_pages_spec.rb
rails generate integration_test static_pages
或者直接手动创建spec/requests/static_pages_spec.rb这个文件.
下面做一个简单的测试

describe "Home page" do
  it "should have the content 'Sample App'" do
    visit '/static_pages/home'
    expect(page).to have_content('Sample App')
  end
end

运行bundle exec rspec spec/requests/static_pages_spec.rb, 运行失败!
因为在app/views/static_pages/home.html.erb文件中并没有"Sample App"关键字.在该文件中,添加上"Sample App"测试就能通过.

4 let和let!的用法

Use let to define a memoized helper method. The value will be cached
across multiple calls in the same example but not across examples.
Note that let is lazy-evaluated: it is not evaluated until the first time
the method it defines is invoked. You can use let! to force the method's
invocation before each example.

http://stackoverflow.com/questions/10173097/rails-rspec-difference-between-let-and-let

使用let来定义一个memoized helper方法。该值可以在多个例子中使用,但不能跨多个实例调用。

  • spec/helpers/let_spec.rb *

    describe "let" do
    other_count = 0
    invocation_order = []

    let(:count) do
    invocation_order << :let!
    other_count += 1
    end

    it "calls the helper method in a before hook" do
    # count
    # invocation_order << :example
    # expect(invocation_order).to eq([:let!,:example])

    invocation_order << :example
    expect(invocation_order).to eq([:example])
    expect(other_count).to      eq(0)
    other_count +=1
    expect(other_count).to      eq(1)
    

    end

it "calls the helper method in a before hook again" do
  # count
  # invocation_order << :example
  # expect(invocation_order).to eq([:let!,:example])  

  invocation_order << :example
  expect(invocation_order).to eq([:example,:example])
  expect(other_count).to      eq(1)
  other_count +=1
  expect(other_count).to      eq(2)

end

end


describe "let test" do
   let(:count) { $count += 1 }
   # 可以使用count
end
describe "no let test" do
    # 不能使用count
end

下面是let!的使用方法

  • spec/helpers/let_bang_spec.rb *

    describe "let!" do
    count = 0
    invocation_order = []

    let!(:count) do
    invocation_order << :let!
    count += 1
    end

    it "calls the helper method in a before hook" do
    invocation_order << :example
    expect(invocation_order).to eq([:let!, :example])
    expect(count).to eq(1)
    end

    it "calls the helper method in a before hook again" do
    invocation_order << :example
    expect(invocation_order).to eq([:let!, :example, :let!, :example])
    expect(count).to eq(2)
    end
    end

5 before用法

http://stackoverflow.com/questions/5974360/rspec-difference-between-let-and-before-block
http://stackoverflow.com/questions/5359558/when-to-use-rspec-let?lq=1
let 和 before(:each)的区别, let不会自动初始化变量,而before(:each)会自动初始化变量.如果我其中的某一个测试用力不需要这些变量,依然需要初始化,如初始化变量需要很多时间,对这个测试的初始化就是浪费的时间和资源.

before(:each)的用法

  • spec/helpers/before_each_spec.rb *

    require "rspec/expectations"
    class Thing
    def widgets
    @widgets ||= []
    end
    end
    describe Thing do
    before(:each) do
    @thing = Thing.new
    end
    describe "initialized in before(:each)" do
    it "has 0 widgets" do
    @thing.should have(0).widgets
    end
    it "can get accept new widgets" do
    @thing.widgets << Object.new
    end
    it "does not share state across examples" do
    @thing.should have(0).widgets
    end
    it "does not have 1 count" do
    @thing.should_not have(1).widgets
    end
    end
    end

before(:all)用法

  • spec/helpers/before_all_spec.rb *

    require "rspec/expectations"
    class Thing
    def widgets
    @widgets ||= []
    end
    end
    describe Thing do
    before(:all) do
    @thing = Thing.new
    end
    describe "initialized in before(:all)" do
    it "has 0 widgets" do
    @thing.should have(0).widgets
    end
    it "can get accept new widgets" do
    @thing.widgets << Object.new
    end
    it "shares state across examples" do
    @thing.should have(1).widgets
    end
    it "should not have 0 widgets" do
    @thing.should_not have(0).widgets
    end
    end
    end

6 model 测试

spec/models/widget_spec.rb

describe Widget do
  context "test fields" do
    before do
      @widget = create(:widget)
    end

    subject{ @widget }
    # respond_to 用来判断属性有没有
    it { should respond_to(:name) }
    it { should respond_to(:email) }
    it { should respond_to(:address) }
    it { should respond_to(:lat) }
    it { should respond_to(:lng) }
    it { should be_valid }

    # 简约的写法
    describe "when name is not present" do
      before { @widget.name = " " }
      it { should_not be_valid }
    end

    # 繁琐的写法
    describe "when name is not present" do
      before { @widget.name = " " }
      # 将it的单行的方式,改写成多行的方式
      it "should not be valid" do
        expect(@widget).not_to   be_valid
      end
    end


    describe "when name is invalid" do
      before { @widget.name = nil }
      it { should have(1).errors_on(:name) }
    end

    describe "when name length > 50 " do
      before do
        @widget.name = "a"*51
      end
      it { should_not be_valid }
    end

    describe "when email is invalid" do
      before { @widget.email = nil }
      it do
        # Email can't be blank, Email is invalid
        should have(2).errors_on(:email)
      end
    end

    describe "when email is invalid" do
      it "should be invalid" do
        # 使用each 循环,遍历非法的email
        addresses = %w[user@foo,com user_at_foo.org example.user@foo.
                     foo@bar_baz.com foo@bar+baz.com]
        addresses.each  do |addr|
          @widget.email = addr
          expect(@widget).not_to  be_valid
        end
      end
    end

    describe "when email is valid" do
      it "should be valid"  do
      # 使用each 循环,遍历可用的email
      addresses = %w[user@foo.COM A_US-ER@f.b.org
                    frst.lst@foo.jp a+b@baz.cn]
        addresses.each do |addr|
          @widget.email = addr
          expect(@widget).to be_valid
        end
      end
    end
   # ========== 上述验证邮箱的可该写成如下
   describe "when email" do
      context "is valid" do 
        it "should be valid"  do
          addresses = %w[user@foo.COM A_US-ER@f.b.org
                   frst.lst@foo.jp a+b@baz.cn]
          addresses.each do |addr|
            @widget.email = addr
            expect(@widget).to be_valid
         end
      end
    end

      context "is invalid" do
        it "should be invalid" do
          addresses = %w[user@foo,com user_at_foo.org example.user@foo.
                  foo@bar_baz.com foo@bar+baz.com]
          addresses.each  do |addr|
            @widget.email = addr
            expect(@widget).not_to  be_valid
          end
        end
    end
  end
end



  context "when initialized" do
    # 创建默认的共享变量
    subject(:widget) { Widget.new(:name => Faker::Name.name,
                                  :email => Faker::Internet.email) }


    it "should increment the Relationship count" do
      expect do
        Widget.create!(:name => "first comment", :email => Faker::Internet.email)
        Widget.create!(:name => "second comment", :email => Faker::Internet.email)
      end.to change(Widget, :count).by(2) # from(1).to(3)
      # https://www.relishapp.com/rspec/rspec-expectations/v/2-0/docs/matchers/expect-change
    end

    # 第一种写法
    it "is a new widget" do
      expect(widget).to be_a_new(Widget)
    end

    # 第二种写法
    it { should be_a_new(Widget) }

    it "is not a new string" do
      expect(widget).not_to be_a_new(String)
    end
  end

  context "when saved" do
    subject(:widget) { create(:widget) }

    it "is not a new widget" do
      expect(widget).not_to be_a_new(Widget)

    end
    it "is not a new string" do
      expect(widget).not_to be_a_new(String)
    end
  end
end
7 controller测试

spec/controllers/widgets_controller_spec.rb

describe WidgetsController do
  # show ============
  describe "GET #show" do
   #• 和控制器动作交互的基本 DSL 句法:每个 HTTP 请求方法都对应于一个方法(本例中的方法
   #是 get) ,其后跟着动作的 Symbol 形式(:show) ,然后是传入的请求参数(id: contact) 。
   #• 控制器动作实例化的变量可以通过 assigns(:variable_name) 方法获取。
   #• 控制器动作的返回结果可以通过 response 获取。
    it "assigns the required 1 to @1" do
      widget = create(:widget)
      get :show, id: widget
      expect(assigns(:widget)).to eq widget
     # http://stackoverflow.com/questions/8616508/what-does-assigns
     # 
    end

    it "renders the :show template "  do
      widget = create(:widget)
      get :show, id: widget
      expect(response).to render_template :show
    end
  end

  # new ============
  describe "GET #new" do
    it "assigns a new Widget to @widget" do
      get :new
      expect(assigns(:widget)).to  be_a_new(Widget)
    end

    it "renders the :new template" do
      get :new
      expect(response).to render_template :new
    end
  end

  # edit ============
  describe "GET #edit" do
    it "assigns the request widget to @widget" do
      widget = create(:widget)
      get :edit, id: widget
      expect(assigns(:widget)).to eq widget
    end

    it "renders the :edit template" do
      widget = create(:widget)
      get :edit, id: widget
      expect(response).to render_template :edit
    end
  end

  # create ============
  describe "POST #create" do
    context "with invalid attributes" do
      # create动作, 在符合 REST 架构的程序中,这个动作可以响应 POST 请求。
      # create 动作和响应 GET 请求的动作最主要的不同点是,不能像 GET 请求那样只传入:id 参数,而
      # 要传入 params[:widget] 对应的值,这个值就是用户可能在表单中输入的内容。
      it "does not save the new widget in the database" do
        expect{
          post :create,
          widget: attributes_for(:invalid_widget)
        }.to change(Widget, :count).by(0)
      end

     # expect的另一种写法
     it "does save the new widget in the database again" do
       expect do
         post :create,
         widget: attributes_for(:widget)
       end.to  change(Widget, :count).by(1)
     end

      it "re-renders the :new template" do
        post :create,
          widget: attributes_for(:invalid_widget)
        expect(response).to render_template :new
      end
    end

    context  "with valid attributes" do
      it "does save the new widget in the database" do
        expect{
          post :create,
          widget: attributes_for(:widget)
        }.to change(Widget, :count).by(1)
      end
      it "re-renders the assigns[:widget] template" do
        post :create,
          widget: attributes_for(:widget)
        expect(response).to redirect_to widget_path(assigns[:widget])
      end
    end

    # update ============
    describe "PATH #update" do
      before :each do
        @widget = create(:widget, name: 'lisi', email: 'lisi@126.com')
      end
      context "valid attribute" do
        # 先测试请求
        it "located the request @widget " do
          patch :update, id: @widget, widget: attributes_for(:widget)
          expect(assigns(:widget)).to  eq(@widget)
        end

        # 其次测试上传的数据,是否发生了变化
        it "changes @widget`s attributes" do
          patch :update, id: @widget,
            widget: attributes_for(:widget, name: "lol", email: "lol@126.com")
          @widget.reload
          expect(@widget.name).to eq("lol")
          expect(@widget.email).to eq("lol@126.com")
        end

        # 最后测试,返回结果
        it "redirect to the updated widget" do
          patch :update, id: @widget, widget: attributes_for(:widget)
          expect(response).to redirect_to @widget
        end
      end

      context "with invalid attributes" do
        it "does not change the contact`s attribues" do
          patch :update, id: @widget,
            widget: attributes_for(:widget, name: nil, email: "nil@126.com")
          @widget.reload
          expect(@widget.name).to eq("lisi")
          expect(@widget.email).not_to eq("nil@126.com")
        end

        it "re-renders the edit template" do
          patch :update, id: @widget,
            widget: attributes_for(:invalid_widget)
          expect(response).to render_template :edit
        end
      end
    end

    # delete ============
    describe "DELETE destroy" do
      before :each do
        @widget = create(:widget)
      end

      it "delete the widget" do
        expect{
          delete :destroy, id: @widget
        }.to change(Widget, :count).by(-1)
      end

      it "redirect to widget#index" do
        delete :destroy, id: @widget
        expect(response).to redirect_to widgets_path
      end
    end
  end


  describe "GET index" do
    it "has a 200 status code" do
      get :index
      expect(response.status).to eq(200)
    end

    it "renders the index template"  do
      get :index
      expect(response).to render_template("index")
      expect(response.body).to eq ""
    end

    it "renders the widgets/index template"  do
      get :index
      expect(response).to  render_template("widgets/index")
      expect(response.body).to eq ""
    end

    it "not renders the 'new' template"  do
      get :index
      expect(response).not_to  render_template("new")
    end
  end
end
8 route测试

首先运行
rails generate controller admin/Accounts index
用这个来创建一个namespace为admin的account_controller.rb

  • spec/routing/admin_routing_spec.rb*

    require "spec_helper"
    describe "routes for Widgets" do
    it "routes /admin/accounts to the admin/accounts controller" do
    expect(get("/admin/accounts")).
    to route_to("admin/accounts#index")
    end
    it "routes /admin/accounts to the admin/accounts controller again" do
    expect(get("/admin/accounts")).
    to route_to(:controller => "admin/accounts", :action => "index")
    end
    end

spec/routing/widgets_routing_spec.rb

 describe "routes for Widgets" do
      it "route to widgets" do
        expect(:get => "/widgets").to be_routable
      end
      it "does not route to widgets/foo/bar" do
        expect(:get => "/widgets/foo/bar").not_to be_routable
      end
 end

*spec/routing/widgets_routing_spec.rb *

it "routes a named route" do
      expect(:get => new_widget_path).
      to route_to(:controller => "widgets", :action => "new")
end
9 factory_girl 的使用

1.修改Gemfile配置

group :development, :test do
    gem 'factory_girl_rails', '4.2.1'
    gem 'ffaker', '~> 1.21.0'
    gem 'rspec-rails', '2.13.1'
 end

并且使用 gem 'jbuilder', '1.5.0'
配置完成之后,执行bundle install

2.在 * app/views/widgets/index.json.jbuilder * 文件中

json.widgets @widgets do |widget|
    json.id         widget.id
    json.name       widget.name
 end

在 * app/views/widgets/show.json.jbuilder*文件中

json.widget do
    json.id        @widget.id
    json.name      @widget.name
end

3.在 spec/factories/widget.rb文件中

FactoryGirl.define do
  factory :widget do
    name { Faker::Name.name }
  end
end

4.在* spec/requests/widgets_spec.rb *文件中

describe "Widgets" do
  describe "GET /widgets" do
    let(:widgets)  { FactoryGirl.create_list(:widget, 10) }
    let(:url)      { "/widgets.json" }
    before do
      WidgetsController.stub(:index).and_return(widgets)
      get(url)
    end
    describe "return JSON" do
      it { MultiJson.decode(response.body)["widgets"].should   have_at_most(10).items }
      it "should be correct" do
        expect(MultiJson.decode(response.body)["widgets"].first["id"]).to      eq(widgets.first.id)
        expect(MultiJson.decode(response.body)["widgets"].first["name"]).to    eq(widgets.first.name)
      end
    end
  end
  describe "specify widget" do
    let(:widget) { FactoryGirl.create(:widget) }
    let(:url) {"/widgets/#{widget.id}.json"}
    before { get(url) }
    it "should be correct" do
      expect(MultiJson.decode(response.body)["widget"]).to         include("id")
      expect(MultiJson.decode(response.body)["widget"]).to         include("name")
    end
    it "should render the deal in JSON format" do
      expect(MultiJson.decode(response.body)["widget"]["id"]).to     eq(widget.id)
      expect(MultiJson.decode(response.body)["widget"]["name"]).to   eq(widget.name)
    end
  end
end

5.在spec/spec_helper.rb文件中配置如下
每次在测试中,都必须使用FactoryGirl.create(xxx) FactoryGirl.create_list(xxx)
在Factory Girl 3.0 开始, 只要做一个设置,Rails就会变得简洁,美好
可以加在spec_helper.rb文件Rspec.configure块中的任何地方

RSpec.configure do |config|
  config.include FactoryGirl::Syntax::Methods
end

然后在测试的代码中,就能使用比较简洁的语法create(xxx) create_list(xxx)
还能使用attributes_for(xxx)等

6.Factory生成文件存放的位置

通过Factory Girl生成器生成的文件都会放在 spec/factories文件夹
中,文件的名字会试用对应模型名字的复数形式(所以Contact
模型的预构件文件是spec/factories/contacts.rb)

10 ffaker的使用

faker在用来创建比较真实的假数据.
ffakerfaker的重写,速度比faker块一些.

修改Gemfile配置

require 'ffaker'

之后执行bundle installl给应用程序安装ffaker gem包.
下面是常用的示例.

Faker::Name.name                       #=> "Christophe Bartell"
Faker::Internet.email                  #=> "kirsten.greenholt@isher.info"
Faker::Geolocation.lat                 #=> 38.4685363769531
Faker::Geolocation.lng                 #=> -87.888795
Faker::Address.street_address          #=> "95519 Evans Oval"
Faker::Lorem.sentence                  #=> "Aut recusandae quam cum omnis aut dolores."
Faker::Lorem.paragraph                 #=> "Earum et est qui id totam adipisci sint. In qui officia velit veritatis rerum consequuntur. Aut earum eaque velit. Numquam minima autem at."
11 mock的使用

为什么要使用mock?
系统总是很复杂,不同的模块功能耦合在一起,A调用B, B调用C甚至A.但是我们在测试A
的某个方法的时候,应该把注意力集中在A这个方法功能上,而没有必要把这个方法中需要的其他方法
(A的或者B,C)都测试一遍,虽然这些其他方法的正确是A的这个方法正确的保证!B,C的方法应该在各自的测试中独立进行.

如一个方法,读取rss,然后解析,生成view

url = "http://forum.rccchina.com/api/posts/meetings.xml"
require 'net/http'
require 'uri'
xml = Net::HTTP.get_print URI.parse(url)

这段代码核心在后面解析生成view的功能,测试的时候,完全不需要真的去从forum.rccchina.com上面获取xml的数据,覆盖Net::HTTP.get_print方法即可。

xml = "<meetings>...</meetings>"
Net::HTTP.stub!(:get_print).and_return(xml)

下面是一个mock的例子

`spec/factories/widget.rb`

FactoryGirl.define do
  factory :widget do
    name  { Faker::Name.name }
    email { Faker::Internet.email }
    tel   { 123456 }

    factory :invalid_widget do
      name  nil
      email nil
    end
  end
end

`spec/first/mock_spec.rb`

require 'spec_helper'

class Hello
  def say
    "hello world"
  end
end

describe Hello do
  context "factory girl spec " do
    let(:widget) { FactoryGirl.create(:widget) }

    subject { widget }

    it { should respond_to(:name) }
    it { should respond_to(:email) }
    it { should respond_to(:tel) }
  end

  context "mock saying hello " do
    before(:each) do
      @hello = mock(Hello)
      @hello.stub!(:say).and_return("hello world")
      @hello.stub!(:sleep).and_return("sleep")
    end

    subject { @hello }

    it { should respond_to(:say) }
    it { should respond_to(:sleep) }
  end


  context "saying hello" do
    before(:each) do
      @hello = mock(Hello)
      @hello.stub!(:say).and_return("hello world")
      @hello.stub!(:sleep).and_return("sleep")
    end


    it "#sleep should return sleep" do
      @hello.should_receive(:sleep).and_return("sleep")
      answer = @hello.sleep
      answer.should match("sleep")
    end


    it "#say should return hello world" do
      @hello.should_receive(:say).and_return("hello world")
      answer = @hello.say
      answer.should match("hello world")
    end

    it "#say should not return zzz"  do
      @hello.should_receive(:say).and_return("hello world")
      answer = @hello.say
      answer.should_not match("zzz")
    end
  end
end

从上述的例子中可以看出,FactoryGirl 是根据模型来模拟真实的字段属性.
而mock可以模拟出模型中不存在的字段属性.mock可以用来虚拟比较复杂的属性

12 shared_examples 的使用

shared_examples 解决在测试用需要复用的代码

包含shared groups 的文件必须在使用前首先被加载,

一种方法是把所有包含shared examples 的文件放到 spec/support目录下
并且在spec/spec_helper.rb中导入他们
Dir["./spec/support/**/*.rb"].each { |f| require f }

spec/helpers/shared_examples_spec.rb

shared_examples 'a collection' do
  let(:collection) { described_class.new([7,2,4]) }

  context 'initialized with 3 items' do
    it "says it has three items" do
      expect(collection.size).to    eq 3
    end
  end

  describe "#include?" do
    context 'with an item that is in the collection' do
      it "returns true" do
        expect(collection).to      include(7)
      end
    end

    context "with an item that is not in the collection" do
      it "returns false" do
        expect(collection).not_to  include(9)
      end
    end
  end
end


describe Array do
  it_behaves_like 'a collection'
end

describe Set do
  it_behaves_like 'a collection'
end

spec/helpers/shared_examples_block_spec.rb

shared_examples 'a collection' do

  context 'initialized with 3 items' do
    it "says it has three items" do
      expect(collection.size).to    eq 3
    end
  end

  describe "#include?" do
    context 'with an item that is in the collection' do
      it "returns true" do
        expect(collection).to      include(7)
      end
    end

    context "with an item that is not in the collection" do
      it "returns false" do
        expect(collection).not_to  include(9)
      end
    end
  end
end


describe Array do
  it_behaves_like 'a collection' do
    let(:collection) { Array.new([7,2,4]) }
  end
end

describe Set do
  it_behaves_like 'a collection' do
    let(:collection) { Set.new([7,2,4]) }
  end
end

spec/helpers/shared_examples_block_params_spec.rb

require 'set'

shared_examples "a measurable object" do |measurement, measurement_methods|
  measurement_methods.each do |measurement_method|
    it "should return #{measurement} from ##{measurement_method}" do
      subject.send(measurement_method).should == measurement
    end
  end
end

describe Array, "with 3 items" do
  subject { [1,2,3] }
  it_should_behave_like "a measurable object", 3, [:size, :length]
end

describe String , "of 6 characters" do
  subject { "FooBar" }
  it_should_behave_like "a measurable object", 6, [:size, :length]
end
13 注意

1

describe User do
  before { @user = User.new(name: "Example User", email: "user@example.com") }
  subject { @user }
  it { should respond_to(:name) }
  it { should respond_to(:email) }
end

ruby代码中的写法
@user.respond_to?(:name)

在RSpec中可以写成

it "should respond to 'name'"  do
  expect(@user).to respond_to(:name)
end

因为指定了subject{ @user } 我们还可以写成单行的形式

it { should respond_to(:name) }

2

require 'spec_helper'
describe Contact do
  describe "filter last name by letter" do
    context "matching letters" do

    end

    context "non-matching letters" do

    end
   end
end

严格上来说describe 和 context 是可以互换的
但是describe 用来表示需要实现的功能,而context针对该功能不同的情况。

3
2012年6月,Rspec开发团队宣布,在v2.11中使用了新句法
来替代传统的should式句法,如

it "is true when true" do
 true.should be_true
end

新句法会把要测试的值传递给 expect() 方法,然后和匹配器比较:
it "is true when true" do
 expect(true).to be_true
end

4

describe "Home page" do
  before { visit root_path }

  it "should have the content 'Sample App'" do
    expect(page).to have_content('Sample App')
  end

  it "should have the base title" do
    expect(page).to have_title("Ruby on Rails Tutorial Sample App")
  end

  it "should not have a custom page title" do
    expect(page).not_to have_title('| Home')
  end
end

使用最新的rspec特性, 将重复的东西删除掉

在代码中的每一个用例都出现下面的东西
it "should have the content 'Sample App'" do
同时还使用了
expect(page).to have_content('Sample App')

二者虽然形式不同,要表达的意思却是相同的.而且两个用例都引用了page变量
使用it方法的另一种形式,把测试代码和描述文本合二为一
我们可以告诉Rspec page就是要测试的对象(subject), 这样就可以避免多次使用page

结果修改变为:

subject { page }
describe "Home page" do
    before { visit root_path }

    it { should have_content('Sample App') }
    it { should have_content('Ruby ...')}
    it { shoule have_content('| Home') }
end

参考:
http://ibugs.github.com/rspec_test
http://www.relishapp.com/rspec
https://github.com/rspec/rspec-expectations
https://github.com/thoughtbot/factory_girl
http://betterspecs.org/

推荐阅读更多精彩内容

  • 加速测试的方法 这里所说的“速度”有两层含义。 其一,当然是测试运行所用的时间。我们这个小程序的测试已经开始出现慢...
  • 加速测试的方法 这里所说的“速度”有两层含义。 其一,当然是测试运行所用的时间。我们这个小程序的测试已经开始出现慢...
  • 话说昨日,健哥让我分享下怎么用rspec写模型的测试,顿时一脸懵逼,因为只会些拳脚猫功夫,赶紧百度谷歌相关知识,七...
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
  • Awesome Ruby Toolbox Awesome A collection of awesome Ruby...